| --- |
| license: mit |
| tags: |
| - security |
| - proof-of-concept |
| - tensorflow-js |
| - denial-of-service |
| --- |
| |
| # TensorFlow.js weights-manifest shape DoS PoC |
|
|
| This repository contains a proof-of-concept malicious `.tfjs`-style weights manifest for a |
| responsibly-disclosed vulnerability in [`tensorflow/tfjs`](https://github.com/tensorflow/tfjs) |
| (tested at commit `7f5309fef0a47545e34049903dbdae0f97285f7e`), reported via huntr's Model File |
| Vulnerability program. |
|
|
| ## What these files are |
|
|
| - `malicious_manifest.json` β a weights manifest declaring a single weight, `malicious_weight`, |
| of `dtype: "string"` and `shape: [500000000]` (500 million). |
| - `weights.bin` β the "backing" weight-data file the manifest points to. It is **completely |
| empty (0 bytes)** β there is no real weight data behind the declared shape at all. |
| - `reproduce.js` β loads the manifest using `@tensorflow/tfjs`'s public, officially-documented |
| `tf.io.weightsLoaderFactory` API (the same pattern shown in that function's own JSDoc example |
| for loading weights from disk in Node.js without the native `tfjs-node` addon) β no internal or |
| private functions are used. |
|
|
| ## What happens when you run it |
|
|
| ``` |
| npm install @tensorflow/tfjs |
| node reproduce.js |
| ``` |
|
|
| The process becomes unresponsive (including a 2-second heartbeat timer in the script itself, which |
| does not fire while the vulnerable loop is running β the entire Node.js event loop is blocked, not |
| just the one loading operation) and, after roughly 3.5 minutes, **crashes outright** with a fatal |
| V8 out-of-memory error (`FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - |
| JavaScript heap out of memory`, heap grown past 4 GB) β not merely a temporary freeze. `500000000` |
| was chosen so the demonstration completes in a few minutes; a real attacker could use an even |
| larger value (e.g. `999999999999`, still just a single JSON integer) for a longer/guaranteed crash |
| against a victim with more available memory. See the reporter's full write-up submitted via huntr |
| for the complete timing data and crash log. |
|
|
| This happens because `getWeightBytelength()` (`tfjs-core/src/io/io_utils.ts`) computes the |
| declared string-tensor size (`sizeFromShape(shape)`, unbounded, no validation) and loops that many |
| times over the weight's binary data β with no check that any real data is actually behind the |
| declared shape, and no early exit when the (in this case entirely empty) backing buffer is |
| exhausted. |
|
|
| ## Scope note |
|
|
| This PoC is provided solely for the purpose of responsible vulnerability disclosure and |
| reproduction by the `tfjs` maintainers / huntr triage team. It is not intended for any other use. |
| Running it will make your own Node.js process unresponsive for the duration of the demo β this is |
| expected and is exactly the behavior being reported; do not run it against a shared or |
| production process. |
|
|