Views
No views yet
torch.load() call:| Format | File head | modelscan behavior |
|---|---|---|
| modern ZIP archive (default) | PK\x03\x04 | scans embedded data.pkl |
| legacy concatenated pickles | \x80\x02\x8a... | scans only the first pickle frame |
| legacy TAR archive | storages\x00... | never scanned (skipped) |
modelscan's PyTorch scanner (scan_pytorch in
modelscan/tools/picklescanner.py) contains an unimplemented TODO for tar
loading:1should_read_directly = _should_read_directly(model.get_stream())
2if should_read_directly and model.get_stream().tell() == 0:
3 try:
4 # TODO: implement loading from tar
5 raise TarError()
6 except TarError:
7 # file does not contain a tar
8 model.get_stream().seek(0)
9
10magic = get_magic_number(model.get_stream())
11if magic != MAGIC_NUMBER:
12 return ScanResults(..., SkipCategories.MAGIC_NUMBER, "Invalid magic number", ...)get_magic_number(). That helper disassembles pickle opcodes looking for the
torch magic long, but a tar file begins with the tar header storages\x00...,
so no magic is found and the entire file is skipped as
"Invalid magic number" — 0 issues, 0 scanned.torch.load(..., weights_only=False) fully supports this legacy tar
format via _legacy_load, which extracts the members storages, tensors,
and pickle and unpickles them — executing any __reduce__ payload in the
pickle member.os.system — with no evasion needed.artifacts/legacy_tar_bypass.pt — malicious tar-format torch fileos.system("touch <marker>").make_poc.py — regenerates the artifactverify.py — self-contained proof: shows modelscan reports 0 issues and that
torch.load(..., weights_only=False) executes the payloadartifacts/legacy_tar_bypass.pt — the PoCartifacts/reproduce_output.txt — captured verification outputartifacts/sha256sums.txt — checksums1pip install "modelscan==0.8.8" "torch==2.13.0+cpu"
2python verify.py[*] head: b'storages' zip? False
[*] modelscan issues=0 scanned=0 skipped=['MAGIC_NUMBER']
[*] marker before load: False, after load: True
RESULT: REPRODUCED ✅modelscan reports 0 issues and does not scan it at all (skipped).torch.load(..., weights_only=False) executes attacker code at load time.scan_pytorch (the existing TODO) and route
the pickle member through scan_pickle_bytes.