PyTorch backward pass can hide turing-complete malware in zero-loss terms
Researchers demonstrate how floating-point precision quirks allow arbitrary code execution inside PyTorch's gradient descent, even when loss metrics report zero.
A new technical demonstration reveals that PyTorch’s automatic differentiation engine can be tricked into executing arbitrary programs during the backward pass of training. By exploiting discrepancies between IEEE 754 floating-point arithmetic and symbolic calculus, an attacker can embed a Turing-complete state machine, such as Conway’s Game of Life, into model weights. This occurs while the reported loss remains exactly zero, evading standard monitoring and validation checks.
What happened
The discovery originated from an investigation into floating-point precision anomalies within PyTorch. The researcher initially sought to determine if a loss function could evaluate to zero mathematically while still triggering weight updates during gradient descent. The result confirmed that it is possible to construct a loss term that logs as 0.0 in every training metric yet forces the optimizer to execute complex logic. This finding transforms a theoretical numerical curiosity into a viable supply-chain attack vector for machine learning pipelines.
The threat model involves a developer fine-tuning an open-source model, such as Meta’s Llama-3-8B. Standard security practices might include verifying SHA-256 hashes of checkpoint files and reviewing Python code for obvious backdoors or system calls. If the training curves appear normal and auxiliary losses remain flat, the environment is typically considered safe. However, an attacker can distribute a malicious package, such as a fake regularizer named UltraLoss, which integrates seamlessly into the training loop without modifying the base checkpoint, architecture, or dataset.
When this malicious loss term is added to the total loss, it reports 0.000000 at every iteration. Despite this benign appearance, the call to total_loss.backward() and optimizer.step() executes an arbitrary program embedded within the model’s weights. The attacker does not need to mutate parameters directly during the forward pass, making the exploit difficult to detect through standard static analysis or runtime logging of loss values.
How it works
The exploit relies on the disagreement between hardware-level floating-point truncation and the symbolic differentiation rules used by autodiff engines. The core mechanism uses a function h(u) = (2^30 + u) - 2^30. In IEEE 754 float32 arithmetic, adding a small value u (where |u| < 64) to 2^30 results in rounding back to 2^30 because the gap between representable numbers at that magnitude is 128. Subtracting 2^30 then yields 0.0. However, PyTorch’s autograd engine treats addition and subtraction symbolically, recording their derivatives as 1. Thus, while the forward pass sees zero, the backward pass computes a gradient of 1.
To create a stealthy gradient source, the researcher combines this behavior with the product rule. A scalar loss is constructed by multiplying two terms: one that evaluates to the target gradient vector in the backward pass but zero in the forward pass, and another that is zero in both passes but has a derivative of 1. Specifically, the loss is defined as the sum of (v - h(v)) * h(theta), where v is the desired weight update. In the forward pass, h(theta) is zero, so the loss is zero. In the backward pass, the product rule allows the saved forward value of the first term to flow through as the gradient, effectively setting theta.grad to the desired update vector.
This mechanism allows the optimizer to overwrite weights with the next state of a cellular automaton, such as Conway’s Game of Life, during each training step. The researcher demonstrated this by wiring the Game of Life rules into 1,024 weights of a CNN trained on MNIST. Over 2,345 steps, the auxiliary loss remained zero, the model achieved normal accuracy, and the weights successfully simulated the automaton without error. The update values are small integers (-1, 0, 1), which have no rounding error in float32, ensuring precise state transitions.
Key details
- The exploit works because IEEE 754
float32rounds small additions to large constants to zero, while PyTorch autograd computes derivatives symbolically as 1. - A loss function can be constructed to evaluate to
0.0in the forward pass while producing an arbitrary gradient vector in the backward pass. - The attack allows Turing-complete programs, such as Conway’s Game of Life, to run inside model weights during standard SGD updates.
- Malicious logic can be hidden using "math-washing," where cellular automaton rules are disguised as geometric regularizers or convolutional layers.
- Attackers can store critical constants, such as kernel weights for the state machine, in external config files claimed to be hyperparameter tuning results.
- Compiled CUDA extensions can further obscure the logic, wrapping malicious binary code in thin Python classes that appear to be standard spectral penalties.
Why it matters
This vulnerability challenges the assumption that monitoring loss metrics and checking file hashes are sufficient for securing ML training pipelines. Developers often trust open-source libraries and regularization techniques without deeply inspecting their mathematical implementation. If a library claims to improve convergence or stability, its internal operations are rarely audited for non-standard gradient behaviors. This exploit shows that a library can behave perfectly normally in terms of output metrics while hijacking the training process itself.
For teams building products with AI, this highlights a blind spot in supply-chain security. Traditional software security focuses on code execution and data access, but ML training introduces a new vector where the optimization algorithm itself becomes the payload carrier. Since the malicious code runs within the legitimate mathematical operations of gradient descent, it bypasses many conventional safeguards. This requires a shift in how engineers validate third-party ML components, moving beyond surface-level metrics to verify the mathematical integrity of loss functions.
What you can do
- Audit any custom loss functions or regularizers for operations involving large constants and small offsets, which may indicate floating-point exploitation.
- Verify that gradients computed during backpropagation align with expected mathematical derivatives, rather than relying solely on loss values.
- Inspect configuration files and external assets loaded by ML libraries for unexplained constants or kernels that do not match documented purposes.
- Prefer using compiled extensions from trusted sources and verify their integrity, as they can hide malicious logic more effectively than Python code.
- Implement runtime checks that compare weight updates against expected norms, flagging unusual patterns even if the loss remains stable.
- Treat third-party ML packages with the same scrutiny as system-level dependencies, recognizing that they can execute arbitrary logic during training.



