CoolFace
Modelpublic

ybgwon96/sdcpp-pickle-5dim-heap-oob-poc

sourceHugging Facemitupdated 3mo agoView on Hugging Face
0likes
Model Card

PoC — stable-diffusion.cpp pickle 5-dimension smuggling → heap OOB write

Proof-of-concept model file for a huntr Model File Vulnerabilities report against `leejet/stable-diffusion.cpp` @ 3590aa8.

This repository contains a deliberately crafted PyTorch checkpoint for authorized security research (responsible disclosure via huntr). It is not a usable model.

The bug (summary)

stable-diffusion.cpp defines SD_MAX_DIMS = 5 (src/model_io/tensor_storage.h) while ggml tensors have at most 4 dimensions. Its pickle loader accepts a 5-element shape tuple, and TensorStorage::nelements() multiplies all five ne[] slots — but the destination-shape check only compares ne[0..3]. An attacker declares a tensor whose first four dims match a real model weight (passing the shape check) while a bogus 5th dim inflates the element count. On the dtype-mismatch load path (src/model_loader.cpp:1123-1165, which lacks the size assertion that guards the dtype-match path), the inflated element count is used as the copy length into a destination buffer sized for only four dimensions → heap out-of-bounds write (convert_tensor, src/model_loader.cpp:176/:1165).

Dynamically validated (parse level)

poc_pickle_5dim.pt maps model.diffusion_model.input_blocks.0.0.weight to a genuine 5-D tensor (8, 320, 4, 3, 3). Driven through sd.cpp's real read_torch_legacy_file():

read_torch_legacy_file -> ok=1 tensors=1
tensor[0] name='...input_blocks.0.0.weight' n_dims=5 ne=[3,3,4,320,8] nelements=92160 type=0
  >>> SMUGGLED: n_dims=5 > 4; ne[4]=8 inflates nelements past the 4-dim destination

nelements = 92160 = 3·3·4·320·8 — 8× a real 4-D destination ([3,3,4,320], 11520). See VALIDATED.md.

Files

filedescription
poc_pickle_5dim.ptreal legacy PyTorch checkpoint with a 5-D tensor (smuggled ne[4]=8).
gen_pickle_5dim_poc.pygenerator (torch); K controls the inflation factor.
parse_harness.cppdrives sd.cpp's real read_torch_legacy_file() and prints the parsed TensorStorage.
VALIDATED.mdbuild + run evidence.

Disclosure

Reported privately via huntr. Suggested fix: reject shapes with more real dimensions than ggml supports (or require ne[i]==1 for i>=4), and validate nbytes() against ggml_nbytes(dst) on all copy paths (including the dtype-mismatch branch), not only the dtype-match branch.