safetensors Validation Bypass PoC β€” Security Research

Responsible disclosure to huntr.com. Do not use for malicious purposes.

Vulnerability Summary

safe_open(framework="pt") in safetensors silently returns a torch.Tensor whose bytes were never validated, despite completing without error.

The HuggingFace Hub cache stores model files as symlinks by design:

~/.cache/huggingface/hub/models--org--model/
  blobs/
    a3f2c1d9...        ← actual bytes (named by hash)
  snapshots/
    main/
      model.safetensors β†’ ../../blobs/a3f2c1d9...   ← SYMLINK

safe_open() makes two independent open(2) syscalls on this symlink path:

  1. Rust File::open(path) β†’ validates header β†’ inode A βœ“
  2. torch.UntypedStorage.from_file(path) β†’ re-opens path β†’ may get inode B βœ—

Any user-level code (malicious pip package, notebook cell) with write access to ~/.cache/huggingface/hub/ can atomically swap the symlink between these two opens β€” bypassing all safetensors validation silently.

Files

File Purpose
poc_hf_realworld.py Primary PoC β€” exact HF Hub cache layout, two separate processes, no torch required
poc_adversarial_impact.py Impact PoC β€” 3 scenarios: moderation bypass, NaN DoS, APT backdoor
poc_toctou.py Secondary PoC β€” racy/deterministic/simulation variants
good_model.safetensors Legitimate model (4Γ—4 float32, 0x00 fill)
malicious_model.safetensors Malicious model (same structure, 0xDE fill)

Reproduce (no torch needed)

pip install safetensors numpy

# Primary: HF Hub cache attack (two processes)
python3 poc_hf_realworld.py

# Impact: see actual model behavior change
python3 poc_adversarial_impact.py

Confirmed Output

[STAGE 1] HuggingFace Hub cache structure created:
  models--google--flan-t5-base/
  β”œβ”€β”€ blobs/
  β”‚   β”œβ”€β”€ a3f2c1d9ee4b72f1...  ← GOOD model (0x00 bytes)
  β”‚   └── deadbeef12345678...  ← EVIL model (0xDE bytes, attacker pre-staged)
  └── snapshots/main/
      └── model.safetensors β†’ ../../blobs/a3f2c1d9...  ← SYMLINK

[VICTIM]   FD₁ open  β†’ inode 12791353
[VICTIM]   Validation β†’ PASSED βœ“ (tensors: ['weight'])

[ATTACKER] Swapping symlink NOW (atomic rename)...
[ATTACKER] Swap complete!

[VICTIM]   FDβ‚‚ open  β†’ inode 12791354

  βœ“ RACE WINDOW CONFIRMED
  inode validated  = 12791353  (what safetensors checked)
  inode torch maps = 12791354  (what the tensor is backed by)
  All 64 bytes of tensor data are attacker-controlled

Affected Component

  • File: bindings/python/src/lib.rs
  • Function: Open::new() lines 554–698
  • Frameworks affected: PyTorch β‰₯ 1.11.0, Paddle β‰₯ 3.1.1
  • NumPy backend: NOT affected
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support