| # PoC: stable-diffusion.cpp custom-embedding heap out-of-bounds write (malicious .safetensors) |
|
|
| **Target:** `leejet/stable-diffusion.cpp` β `Conditioner::load_embedding` |
| (`src/conditioning/conditioner.hpp:250-253`). |
| **Format:** SafeTensors (`.safetensors`) textual-inversion embedding (also `.pt`/`.ckpt`/`.gguf`). |
| **Class:** CWE-787 heap out-of-bounds write (attacker-controlled length + content), CWE-131 / CWE-682 |
| (incorrect buffer-offset calculation across mixed tensor dtypes). |
| **Verified:** source @ `68f3d6df9f1964e7f942dd242cc9c21b76fa273d`; SIGSEGV reproduced (`CRASH_PROVEN.md`). |
|
|
| ## What it is |
|
|
| Custom embeddings are accumulated into one shared `std::vector<uint8_t> token_embed_custom`, with |
| `num_custom_embeddings` counting the total rows added so far β **both persist across calls**. Each |
| `load_embedding` grows the buffer by `ggml_nbytes(embd)` (this file's dtype) but computes the write |
| offset as `num_custom_embeddings * hidden_size * ggml_type_size(embd->type)` β using **this** file's |
| dtype size for **all** previously-added rows. If an earlier embedding used a smaller dtype (F16, 2 B) |
| and the current one a larger dtype (F32, 4 B), the offset over-counts the earlier rows and the |
| `memcpy` writes past the freshly-resized buffer β an OOB heap write whose size scales with the first |
| embedding's row count (attacker-controlled) and whose bytes are the second embedding's contents. |
|
|
| ## Files |
|
|
| - `embd_f16.safetensors` β first embedding, dtype F16, shape `[8000, 768]`. |
| - `embd_f32.safetensors` β second embedding, dtype F32, shape `[1, 768]`. |
| - `make_embeddings.py` β regenerates both (`python make_embeddings.py <R0> <outdir>`); `R0` sets the |
| overflow size. |
| - `embd_dtype_oob_repro.cpp` / `embd_dtype_oob_repro` β isolated reproduction of the exact |
| `resize`+`memcpy` arithmetic; SIGSEGVs at `-O0`. |
| - `CRASH_PROVEN.md` β run output + lldb backtrace + the offset math. |
|
|
| ## Trigger (real sd.cpp) |
|
|
| Place both files in the embeddings dir and reference both tokens in one prompt, e.g. |
| `sd -m model.safetensors --embd-dir . -p "a photo, embd_f16 embd_f32"` β `load_embedding` is called |
| for `embd_f16` (F16) then `embd_f32` (F32); the second call performs the OOB write. |
|
|
| Coordinated-disclosure security PoC for the huntr AI/ML bug-bounty program. No payload; it only |
| demonstrates the memory-safety flaw. |
|
|