Skip to content

Forbid inductor::_reinterpret_tensor (as_strided heap-OOB bypass)#3079

Draft
edsavage wants to merge 9 commits into
elastic:mainfrom
edsavage:security/forbid-reinterpret-tensor
Draft

Forbid inductor::_reinterpret_tensor (as_strided heap-OOB bypass)#3079
edsavage wants to merge 9 commits into
elastic:mainfrom
edsavage:security/forbid-reinterpret-tensor

Conversation

@edsavage

@edsavage edsavage commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Hardening for elastic/security#12242 (HackerOne): after aten::as_strided was forbidden, a malicious TorchScript model can still get the same class of unchecked storage-offset OOB heap read/write via torch.ops.inductor._reinterpret_tensor → TorchScript op inductor::_reinterpret_tensor (serialised as ops.inductor._reinterpret_tensor(...)).

Verification (local, built pytorch_inference)

Against the Mar-30 distribution binary with graph validation:

Mode Result
Validation on Clean reject: Unrecognised operations: inductor::_reinterpret_tensor (not yet in FORBIDDEN on that build)
--skipModelValidation Model loads successfully
Same .pt + torch.jit.load + forward() in Python Process abort (SIGABRT / exit 134) — confirms upstream memory bug

With this PR, the post-load path fail-fasts as forbidden (not merely unrecognised), and the pre-load scan rejects before torch::jit::load() (including __setstate__ fixtures).

Changes

  • CSupportedOperations::FORBIDDEN_OPERATIONS: add inductor::_reinterpret_tensor.
  • Pre-load scan: match ops.inductor._reinterpret_tensor( in serialised code/**/*.py.
  • Fixtures: OOB read, OOB write, and __setstate__ variants + generator entries.
  • C++ unit tests + test_pytorch_inference_evil_models.py registrations.

Stacking

This branch is stacked on #3078 (pre-load __setstate__ scan). The PR diff vs main therefore includes those commits as well. #3078 should be merged first, then rebasing this; or review/merge as the combined security hardening.

Test plan

  • Local reject-vs-crash verification (table above).
  • Extended pre-load needle scan hits all three new fixtures; e5_with_norm.pt still clean.
  • CI: new testPreLoadScanDetectsReinterpret* / testMaliciousReinterpretTensorRejectedPostLoad green.

Made with Cursor

edsavage and others added 7 commits July 21, 2026 09:26
TorchScript runs a module's __setstate__ during torch::jit::load(), before the
loaded module reaches CModelGraphValidator (which only walks the inlined forward
graph). A forbidden op hidden in __setstate__ is therefore invisible to the
validator and has already executed by the time validation would run.

Add fixtures and a self-contained repro that demonstrate the gap:
- generate_malicious_models.py: add SetStateFileReaderModel and a submodule
  variant (aten::from_file in __setstate__, benign forward); surface
  __setstate__ ops in the generator output.
- test/test_setstate_load_timing.py: torch-only, build-free repro proving
  load-time execution (benign print probe) and op-hiding (static inspection).
- test_pytorch_inference_evil_models.py: register the fixtures as regression
  guards for the forthcoming pre-load scan.

No fix yet; this only reproduces and documents the vulnerability.

Co-authored-by: Cursor <cursoragent@cursor.com>
Close the load-time execution gap: torch::jit::load runs a module's
__setstate__ during deserialization, before CModelGraphValidator (which walks
an already-loaded module's graph) can run. A forbidden op hidden in
__setstate__ therefore executes at load time and never appears in the forward
graph the validator inspects.

Add CModelGraphValidator::scanSerialisedCodeForForbiddenOps, which uses a
PyTorchStreamReader over the buffered archive bytes to textually scan the
serialised TorchScript code (code/**/*.py, including every submodule's
__setstate__) for the forbidden aten ops, WITHOUT loading the model. Main.cc
runs this scan before torch::jit::load and rejects via HANDLE_FATAL, so no
model code executes. Expose the buffered bytes via CBufferedIStreamAdapter::buffer().

This is a defense-in-depth textual check targeted at the curated forbidden set
(from_file, as_strided, save); the post-load allowlist validation is unchanged,
and seccomp remains the syscall backstop. Unit tests cover both setstate
fixtures, forward-graph attacks, a benign model (no false positive), and
malformed input; the binary integration test now expects the setstate models to
be rejected with the forbidden-operations message.

Co-authored-by: Cursor <cursoragent@cursor.com>
Address Copilot review: the benign probe must load cleanly for the marker to
prove load-time execution. Require returncode == 0 and that LOAD_RETURNED was
printed, and print returncode + stderr tail on failure, so a crash that happens
to print the marker first is no longer a false positive.

Co-authored-by: Cursor <cursoragent@cursor.com>
Close the heap OOB bypass reported in elastic/security#12242: TorchInductor's
_reinterpret_tensor takes an unchecked storage offset and enables the same
class of OOB read/write previously mitigated by forbidding aten::as_strided.

- Add inductor::_reinterpret_tensor to FORBIDDEN_OPERATIONS (fail-fast).
- Extend the pre-load serialised-code scan to match
  ops.inductor._reinterpret_tensor( so __setstate__ payloads are rejected
  before torch::jit::load().
- Add OOB-read, OOB-write, and setstate fixtures + unit/integration tests.

Verified against a built pytorch_inference: with validation on, models are
rejected; with --skipModelValidation, load succeeds and Python forward of the
same PoC aborts (SIGABRT), confirming the upstream memory bug.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@edsavage edsavage added the :ml label Jul 21, 2026
Collapse the inductor:: branch condition onto one line to satisfy
clang-format 5.0.1 (CI check_style).

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants