| # Tensorizer pre-authentication Argon2 work-factor amplification |
|
|
| Status: reproduced on Tensorizer `v2.12.1`, |
| commit `652d3c016c84836bba97153e108821c11428ac40`. |
|
|
| ## Summary |
|
|
| An encrypted `.tensors` file controls the Argon2 `opslimit` and `memlimit` |
| values used by `TensorDeserializer` when a caller supplies a password through |
| `DecryptionParams.from_string()`. Tensorizer accepts work factors up to |
| 4,294,967,295 operations and 4,398,046,510,080 bytes of memory. It runs the |
| attacker-selected key derivation before any tensor MAC can reject the modified |
| file. |
|
|
| A 262,440-byte file in this proof changes only the serialized values in the |
| 20-byte PWHash parameter block; `cmp` reports three differing bytes. The |
| control requests 8 KiB and one operation. The trigger requests 256 MiB and |
| three operations. Across three fresh runs: |
|
|
| | Case | Exit | Peak RSS | |
| | --- | ---: | ---: | |
| | Control 1 | 0 | 232,456,192 bytes | |
| | Trigger 1 | 1, after Argon2 and MAC rejection | 483,901,440 bytes | |
| | Control 2 | 0 | 231,702,528 bytes | |
| | Trigger 2 | 1, after Argon2 and MAC rejection | 468,877,312 bytes | |
| | Control 3 | 0 | 232,341,504 bytes | |
| | Trigger 3 | 1, after Argon2 and MAC rejection | 484,343,808 bytes | |
|
|
| The trigger adds approximately 237–252 MiB of resident memory before it is |
| rejected. An attacker can request much larger valid work factors. |
|
|
| ## Root cause |
|
|
| 1. `tensorizer/_crypt_info.py:151-157` decodes the file-controlled |
| `opslimit` and `memlimit` as unsigned 64-bit integers. |
| 2. `tensorizer/serialization.py:1469-1480` passes those values to |
| `EncryptionParams.from_string()`. |
| 3. `tensorizer/_crypt/_encryption.py:523-537` accepts the complete libsodium |
| range, including a 4-TiB memory ceiling, without an application policy cap. |
| 4. `tensorizer/serialization.py:3126-3130` derives the key before |
| `_stream_decrypt()` authenticates the tensor ciphertext. |
|
|
| The issue applies even when hash verification is enabled because key derivation |
| necessarily occurs before the ciphertext MAC and tensor hashes are checked. |
|
|
| ## Reproduction |
|
|
| Install Tensorizer from the pinned commit and the dependencies in |
| `requirements.txt`, then run: |
|
|
| ```bash |
| TENSORIZER_SOURCE=/path/to/tensorizer \ |
| PYTHON_BIN=python3 \ |
| ./reproduce.sh |
| ``` |
|
|
| The script: |
|
|
| 1. serializes a four-element encrypted tensor using the minimum Argon2 work |
| factors; |
| 2. creates a trigger by changing only the serialized PWHash parameters; |
| 3. deserializes both files three times in fresh child processes; and |
| 4. prints JSON containing file size, exit status, elapsed time, and peak RSS. |
|
|
| The control decrypts and prints `sum=6.0`. The trigger consumes the selected |
| Argon2 resources and is rejected only afterward with |
| `Tensor decryption failed`. |
|
|
| ## Security impact |
|
|
| Tensorizer documents loading models directly from HTTP, HTTPS, and S3. A |
| malicious or replaced encrypted model can therefore impose attacker-selected |
| memory and CPU work on an inference worker, model-conversion service, or other |
| automated loader that uses a password. At the published maximums, a single |
| small model can exhaust memory or monopolize CPU before authentication. |
|
|
| ## Prior-art distinction |
|
|
| Tensorizer issue `#154` and PR `#66` broadly disclose that metadata and headers |
| are not authenticated. They do not identify the PWHash work-factor fields as a |
| pre-authentication resource-exhaustion sink, demonstrate this attack, or |
| propose a work-factor cap. PR `#208` concerns the separate tensor-header-length |
| allocation path. |
|
|
| ## Suggested remediation |
|
|
| Require caller-configured maximum KDF work factors during deserialization and |
| reject file values above those limits before invoking Argon2. Conservative |
| defaults should not exceed Tensorizer's documented interactive or moderate |
| profiles. Authenticate the KDF parameter block through a trusted outer header |
| or manifest where deployment architecture permits it. |
|
|