trevdatastreams's picture
Add Tensorizer Argon2 amplification reproducer
28749e4 verified
|
Raw
History Blame Contribute Delete
3.85 kB
# 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.