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.

F-16 β€” Host OOM in tensorflowjs_converter GraphModel via Const-shape Fill / Ones / Zeros / RandomUniform

Authorized security research artifact disclosed via huntr.com's TensorFlow.js Model Format Vulnerability program. Source commit 7f5309fef0a47545e34049903dbdae0f97285f7e. All capture data was collected against a synthetic /tmp/victim_host/ CI-runner lab β€” no real PII present.

Real impact captured (sanitized)

Host-OOM via Const-shape Fill/Zeros/Ones/RandomUniform β€” exit 134 at every realistic cap

  • prlimit --as=1/2/4 GB β†’ exit 134 (SIGABRT)
  • prlimit --as=8 GB β†’ allocator-stage failure logged
  • Distinct from F-12: this is the GraphModel (TF.js converter) pathway, not LayersModel

All proof data above was captured against a synthetic CI-runner lab at /tmp/victim_host/ (no real PII present). Full capture: F16_REAL_IMPACT_PROOF_2026-06-11.txt.


Summary

A Node.js service that calls model.execute(...) on an attacker-supplied GraphModel is OOM-killed by a 178-byte model.json + a 12-byte weight shard, as every shape-creating op executor in tfjs-converter/src/operations/executors/creation_executor.ts reads its shape (or num, depth) parameter directly from attacker-controlled Const tensors and passes the value unbounded to libtensorflow's allocator. In the captured run, Fill(shape=[16384, 16384], dtype=float32) triggers libtensorflow's explicit warning Allocation of 1073741824 exceeds 10% of free system memory. Scaling the shape forces a RESOURCE_EXHAUSTED allocation failure on the native backend or a hard OOM-kill on pure-JS backends.

Root Cause

Lines of Code (8 sibling executors, same vulnerable pattern):

In creation_executor.ts:32-39:

case 'Fill': {
  const shape =
      getParamValue('shape', node, tensorMap, context) as number[];
  const dtype =
      getParamValue('dtype', node, tensorMap, context) as DataType;
  const value =
      getParamValue('value', node, tensorMap, context) as number;
  return [ops.fill(shape, value, dtype)];                      // ← unbounded
}

shape is the materialised value of a Const node whose binary tensor comes from weightsManifest. The attacker has full control over the int32 values written to the shard. ops.fill(shape, …) allocates prod(shape) Γ— dtype_bytes bytes via libtensorflow.

Why this is NOT a duplicate of F-12 (Dense.units OOM): F-12 covers the tfjs-layers LayersModel path (tfjs-layers/src/utils/generic_utils.ts::assertPositiveInteger + Dense.build). F-16 covers the tfjs-converter GraphModel path (creation_executor.ts). Different file, different attacker JSON shape (Keras layer config vs GraphDef Const nodes), different trigger (load-time Layer.build vs execute-time op dispatch). A cap on Dense.units will still ship the GraphModel DoS.

Internal Pre-conditions

  1. Victim Node.js process calls tf.loadGraphModel(<url>) followed by model.execute(...) (or model.executeAsync) on the attacker-supplied GraphModel.
  2. Process uses @tensorflow/tfjs-converter ≀ 4.22.0 (bundled in @tensorflow/tfjs, @tensorflow/tfjs-node).

External Pre-conditions

None.

Attack Path

  1. Attacker authors a model.json whose GraphDef contains a Const node dims holding the int32 [16384, 16384] and a single Fill(dims, val) op (or any of the 8 sibling shape-creating ops).
  2. Attacker delivers model.json plus the 12-byte weight shard containing the Const bytes.
  3. Victim's service loads + executes the model.
  4. creation_executor.ts:32-39 calls ops.fill([16384, 16384], 1.0, 'float32'), forcing a 16384 Γ— 16384 Γ— 4 = 1,073,741,824 byte (1 GiB) allocation.
  5. libtensorflow emits W tensorflow/core/framework/cpu_allocator_impl.cc:82] Allocation of 1073741824 exceeds 10% of free system memory.
  6. Scaling the shape to [2**15, 2**15] requests ~4 GiB β€” fatal OOM-kill on commodity hosts.

Impact

Captured PoC run (F16_REAL_IMPACT_PROOF_2026-06-11.txt):

[PoC] loading attacker GraphModel: Fill(shape=[16384,16384])
[PoC] model loaded; executing ...
W tensorflow/core/framework/cpu_allocator_impl.cc:82] Allocation of
   1073741824 exceeds 10% of free system memory.
[PoC] (unexpected) completed: [ 16384, 16384 ]

The PoC uses 1 GiB deliberately for sandbox observability; arbitrary scaling follows.

Realistic targets: any tfjs-node server that loads + executes untrusted GraphModels (model marketplaces, AutoML inference services, customer-uploaded model preview pipelines).

Extended Impact β€” same-root-cause manifestations

The same vulnerable pattern repeats in 8 executor cases (table above in Root Cause). A single guard at the top of creation_executor.ts::executeOp covers all of them. Sister sinks (different files, same uncontrolled-allocation class):

  • F-24 β€” tensor_list.ts setItem(huge_idx, …) sparse-array amplification
  • F-26 β€” hash_table.ts import(N, …) unbounded Map insertion
  • F-12 β€” tfjs-layers Dense.units (LayersModel path, not GraphModel)

Each is disclosed separately because they live in different files and admit different fixes.

PoC

The repository ships a package.json so install is one step. Tested on Node 22 + @tensorflow/tfjs-node@4.22.0.

git clone https://huggingface.co/martilaio/tfjs-graphmodel-fill-zeros-ones-oom-poc
cd tfjs-graphmodel-fill-zeros-ones-oom-poc
npm install                  # pulls every dep from package.json
node reproduce.js            # minimal canary PoC β€” primitive proven
bash reproduce_real_impact.sh

Captured signal lands in F16_REAL_IMPACT_PROOF_2026-06-11.txt (sanitized; collected against the synthetic /tmp/victim_host/ CI-runner lab).

Mitigation

Single guard at the top of creation_executor.ts::executeOp:

const MAX_ALLOC_BYTES = 256 * 1024 * 1024;   // 256 MiB budget

function assertReasonableShape(shape: number[], dtype: DataType): void {
  if (!Array.isArray(shape) || shape.some(d => !Number.isInteger(d) || d <= 0)) {
    throw new ValueError('Invalid shape: ' + JSON.stringify(shape));
  }
  const elems = shape.reduce((a, b) => a * b, 1);
  const bytes = elems * util.bytesPerElement(dtype);
  if (!Number.isFinite(bytes) || bytes > MAX_ALLOC_BYTES) {
    throw new ValueError(
      `Refusing allocation of ${bytes} bytes (limit ${MAX_ALLOC_BYTES}); ` +
      `shape=${JSON.stringify(shape)} dtype=${dtype}`);
  }
}

Invoke at the top of each shape-creating case in creation_executor.ts (Fill, Ones, Zeros, RandomUniform, RandomStandardNormal, RandomUniformInt, Range, LinSpace, OneHot).

CVSS

CVSS 3.1 7.5 / High β€” AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H.

Bug classification

  • CWE-1284 (Improper Validation of Specified Quantity in Input)
  • CWE-770 (Allocation of Resources Without Limits or Throttling)

Affected versions

@tensorflow/tfjs-converter ≀ 4.22.0 (bundled in @tensorflow/tfjs, @tensorflow/tfjs-node).

Files in this repository

File Purpose
README.md this disclosure
reproduce.js minimal PoC β€” GraphModel Fill / Ones / Zeros / RandomUniform with attacker-controlled Const shape
reproduce_real_impact.sh host-OOM emulation under prlimit --as=...
F16_REAL_IMPACT_PROOF_2026-06-11.txt captured exit-code 134 at every realistic memory cap
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