F-24 β 436,000Γ RSS amplification in tensorflowjs_converter TensorListReserve via sparse high index
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)
261 MB RSS from ~3 KB attacker input β 436,000Γ amplification
- Measured
261 MBpeak RSS in the child prlimit --as=2/4 GBβ exit 134 (SIGABRT)- Trigger:
TensorListReservewith sparse high index
All proof data above was captured against a synthetic CI-runner lab at /tmp/victim_host/ (no real PII present). Full capture: F24_REAL_IMPACT_PROOF_2026-06-11.txt.
Summary
A Node.js service that executes an attacker-supplied GraphModel containing
the sequence TensorListReserve β TensorListSetItem(huge_index, β¦) β TensorListStack (or any downstream TensorList consumer) allocates
hundreds of megabytes of RSS for a 600-byte model.json + a 12-byte weight
shard. Amplification factor β 436,000Γ. Scaling the index to 2**28
requests ~16 GB β fatal OOM-kill on commodity hosts.
The root cause is the maxNumElements === -1 short-circuit in
tensor_list.ts:setItem combined with TensorListReserve initialising
lists with exactly that sentinel β leaving the bounds check inoperative.
An attacker SetItem(index = 2**24, β¦) causes this.tensors[16777216] = tensor, which V8 materialises as a sparse array of length 16,777,217.
Downstream consumers walk the sparse range and either consume O(length)
memory or crash on undefined.rank.
Root Cause
Lines of Code:
- tfjs-converter/src/executor/tensor_list.ts L232-L241 (
setItem, the broken bounds check) - tfjs-converter/src/executor/tensor_list.ts L178-L190 (
resizeβ same short-circuit) - tfjs-converter/src/executor/tensor_list.ts L55-L69 (
TensorListconstructor βmaxNumElements = -1default) - tfjs-converter/src/operations/executors/control_executor.ts L280-L292 (
TensorListReservesetsmaxNumElements = -1)
In tensor_list.ts:232-241:
setItem(elementIndex: number, tensor: Tensor) {
...
if (elementIndex < 0 ||
this.maxNumElements !== -1 && elementIndex >= this.maxNumElements) {
throw new Error(...); // β skipped when maxNumElements === -1
}
...
this.tensors[elementIndex] = tensor; // β V8 sparse alloc for huge indices
}
TensorListReserve (the attacker's preferred constructor) sets
maxNumElements = -1, leaving the guard inoperative. The same
short-circuit appears in resize at L184.
Internal Pre-conditions
- Victim Node.js process calls
tf.loadGraphModel(<attacker URL>)thenmodel.executeAsync(...). - Service uses
@tensorflow/tfjs-converterβ€ 4.22.0.
External Pre-conditions
None.
Attack Path
- Attacker authors
model.jsonwhose GraphDef contains:TensorListReserve(elem_shape=[1], num_init=1)β creates list withmaxNumElements = -1.TensorListSetItem(list, huge_idx = 2**24, val)β sets the sparse index.TensorListStackβ consumes the list.
- Attacker delivers
model.json+ 12-byte weight shard. - Victim service loads + executes the model.
setItemshort-circuits (maxNumElements === -1), assignsthis.tensors[16777216] = tensor. V8 allocates a sparse array of length 16,777,217.TensorListStack'sinferElementShapeiterates the sparse range β either consumes O(length) RSS or crashes onundefined.rankat index 0.
Impact
Captured PoC (F24_REAL_IMPACT_PROOF_2026-06-11.txt):
attacker SetItem index : 2**24 = 16777216
reserve maxNumElements : -1 (set by TensorListReserve)
after 0.5s β err: Cannot read properties of undefined (reading 'rank')
RSS delta : 262 MB
SPARSE-INDEX DOS : YES βββ
- Attacker model.json size: 600 bytes + 12-byte shard.
- RSS delta: 262 MB.
- Amplification factor β 436,000Γ.
- Scaling
HUGE_INDEXto2**28requests ~16 GB RSS β fatal OOM-kill on commodity hosts.
The PoC uses 2**24 for sandbox observability.
Mitigation
Either drop the maxNumElements !== -1 short-circuit OR enforce a hard
ceiling on element index:
const MAX_SAFE_LIST_LEN = 1 << 20; // ~1 M elements
setItem(elementIndex: number, tensor: Tensor) {
...
if (elementIndex < 0 || elementIndex >= MAX_SAFE_LIST_LEN ||
(this.maxNumElements !== -1 && elementIndex >= this.maxNumElements)) {
throw new Error(`Tried to set out-of-bounds index ${elementIndex}`);
}
...
}
Apply the same change to resize at L184. Optionally in
TensorListReserve (control_executor.ts:280-292), refuse
maxNumElements === -1 unless the GraphDef carries an explicit
"unbounded" attribute.
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-770 (Allocation of Resources Without Limits)
- CWE-755 (Improper Handling of Exceptional Conditions β downstream
undefined.rankcrash)
Affected versions
@tensorflow/tfjs-converter β€ 4.22.0.
Files in this repository
| File | Purpose |
|---|---|
README.md |
this disclosure |
package.json |
npm dependencies for one-step npm install |
reproduce.js |
minimal PoC β TensorListReserve with sparse high index |
reproduce_real_impact.sh |
host-OOM emulation + RSS measurement under prlimit --as=... |
F24_REAL_IMPACT_PROOF_2026-06-11.txt |
captured 261 MB peak RSS + exit-code 134 at 2/4 GB caps |