You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

leejet/stable-diffusion.cpp โ€” GGUFReader fallback uncaught-throw std::terminate DoS

Target repo: https://github.com/leejet/stable-diffusion.cpp Pin: b2906939774dc73453467215c80390404d0a2701 (2026-07-17, "feat: add PiD 1.5 support (#1790)") Format: GGML/GGUF, bespoke fallback reader (GGUFReader, src/model_io/gguf_reader_ext.h) Class: CWE-248 Uncaught Exception -> std::terminate() (process abort), deterministic, RAM-independent, from a 32-byte input file.

Summary

stable-diffusion.cpp tries the strict ggml GGUF parser first (gguf_init_from_file, ggml/src/gguf.cpp). If that call fails for any reason โ€” including a simple unsupported-version rejection โ€” it silently falls back to a bespoke, untried reader class (GGUFReader::load, src/model_io/gguf_reader_ext.h) with no try/catch anywhere on the call path back to main().

GGUFReader::read_tensor_info() reads an attacker-controlled 64-bit name_len directly from the file and immediately does:

// src/model_io/gguf_reader_ext.h:142
info.name.resize(name_len);

For name_len == 0xFFFFFFFFFFFFFFFF (> std::string::max_size()), libstdc++ throws std::length_error before attempting any allocation โ€” fully deterministic, independent of available RAM. std::length_error (like std::bad_alloc) does not derive from std::runtime_error, so it escapes the only handler present on the load path:

// src/model_io/gguf_io.cpp:213-220
try {
    for (uint64_t i = 0; i < tensor_count; i++) {
        tensors_.push_back(read_tensor_info(fin));
    }
} catch (const std::runtime_error& e) {   // does NOT catch length_error/bad_alloc
    LOG_ERROR("%s", e.what());
    return false;
}

There is no catch(...) anywhere between this point and main() (read_gguf_file -> ModelLoader::init_from_gguf_file -> ModelLoader::init_from_file -> new_sd_ctx -> CLI main()), so the exception propagates uncaught and the process calls std::terminate() / abort() (SIGABRT).

A secondary variant uses a large-but-technically-allocatable name_len (e.g. 64 GiB) to drive a real allocation attempt via std::string::resize() (value-initializing commit); this typically also throws (std::bad_alloc, likewise uncaught -> terminate) but is included to document the โ€œresize commits real memoryโ€ side of the primitive.

Secondary sink

The same pattern repeats two lines later for tensor shape:

// src/model_io/gguf_reader_ext.h:150
info.shape.resize(n_dims);

n_dims is a uint32_t read straight from the file; std::vector<int64_t>::resize() on a huge n_dims (up to ~4.29e9) both value-initializes (a real commit) and can itself throw length_error/bad_alloc, uncaught for the same reason. Note the GGML_MAX_DIMS clamp (line 156) is applied after this resize, so it provides no protection here.

Reachability (confirmed, not assumed)

  1. gguf_init_from_file() (strict parser, ggml/src/gguf.cpp) rejects any GGUF version field > GGUF_VERSION (GGUF_VERSION == 3, ggml/include/gguf.h:42) and returns NULL (ggml/src/gguf.cpp:505-509).
  2. On NULL, read_gguf_file() (src/model_io/gguf_io.cpp:50-53) unconditionally falls back to GGUFReader gguf_reader; gguf_reader.load(file_path); โ€” no additional validation, no guard.
  3. GGUFReader::load() parses metadata (skipped here via metadata_kv_count = 0) then calls read_tensor_info() for each of tensor_count tensors, which is where the crash sink lives.
  4. read_gguf_file() is called from ModelLoader::init_from_gguf_file() (src/model_loader.cpp:292), itself reached from ModelLoader::init_from_file() (src/model_loader.cpp:237, dispatched whenever is_gguf_file(file_path) is true, i.e. the file starts with the "GGUF" magic โ€” no other validation before dispatch), reached from new_sd_ctx() (src/stable-diffusion.cpp:3580, called at src/stable-diffusion.cpp:~703 on sd_ctx_params->model_path), reached from the official CLI main() (examples/cli/main.cpp:854) on the -m/--model argument.

All of the above was exercised end-to-end using the real, unmodified, officially-built sd-cli binary โ€” not a synthetic harness โ€” built from source at the pinned commit.

PoC files (craft_poc.py)

file size name_len effect
poc_length_error.gguf 32 bytes 0xFFFFFFFFFFFFFFFF deterministic std::length_error, no allocation attempted, RAM-independent
poc_bad_alloc.gguf 32 bytes 0x0000010000000000 (64 GiB) std::bad_alloc (secondary/allocation-commit variant)
control_valid.gguf 80 bytes n/a negative control: well-formed v3 GGUF with 1 real tensor; accepted by the strict parser directly (fallback never fires); loader reports a clean, caught error ("get sd version from file failed") and exits 1 โ€” proves the crash is specific to the malformed fallback input, not a generic loader failure

File layout (see craft_poc.py for full byte-level comments):

offset 0x00  "GGUF"                magic
offset 0x04  u32 version = 4       unsupported -> strict parser rejects -> fallback fires
offset 0x08  u64 tensor_count = 1
offset 0x10  u64 metadata_kv_count = 0
offset 0x18  u64 name_len = 0xFFFFFFFFFFFFFFFF   (tensor #0's name_len; crash sink)

Build (WSL2 Ubuntu 22.04, gcc 11.4.0, cmake 3.22.1)

git clone https://github.com/leejet/stable-diffusion.cpp.git repo
cd repo
git checkout b2906939774dc73453467215c80390404d0a2701
git submodule update --init ggml
mkdir build && cd build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release \
      -DSD_BUILD_EXAMPLES=ON -DSD_WEBP=OFF -DSD_WEBM=OFF ..
make -j$(nproc) sd-cli
# -> build/bin/sd-cli  (copied here as sd-cli_b290693)

WebP/WebM image-I/O submodules are disabled (-DSD_WEBP=OFF -DSD_WEBM=OFF) purely to speed up the build; they are unrelated to the GGUF model loader and not required to reach the crash.

Reproduce

./sd-cli_b290693 -m poc_length_error.gguf -p test
# terminate called after throwing an instance of 'std::length_error'
#   what():  basic_string::_M_replace_aux
# -> SIGABRT (exit code 134 / "Aborted (core dumped)")

./sd-cli_b290693 -m poc_bad_alloc.gguf -p test
# terminate called after throwing an instance of 'std::bad_alloc'
#   what():  std::bad_alloc
# -> SIGABRT (exit code 134)

./sd-cli_b290693 -m control_valid.gguf -p test
# [ERROR] stable-diffusion.cpp:841 - get sd version from file failed: '...'
# [INFO ] main.cpp:857 - new_sd_ctx_t failed
# -> clean exit code 1, no crash (negative control)

Captured logs from the actual runs: crash_length_error.log, crash_bad_alloc.log, control_valid.log.

Dedup note

This is a bespoke sd.cpp-only reader class (src/model_io/gguf_reader_ext.h), introduced as a fallback path distinct from upstream ggml's own gguf_fread_str/gguf.cpp string-reading code (which has its own, separately-patched history). The uncaught-length_error shape here is specific to this fallback class's use of std::string::resize()/std::vector::resize() directly on an attacker u64 with no bounds check and no matching catch clause; not the same mechanism or codebase as prior ggml GGUF string-length findings.

Impact

Any consumer of stable-diffusion.cpp that loads a user-supplied or untrusted .gguf model file (CLI, server example, or any embedding application) can be crashed (denial of service, full process abort) by a 32-byte file, deterministically, with no dependency on available memory, whenever the strict GGUF parser rejects the file for a reason as innocuous as an unsupported/future version number.

Downloads last month
-
GGUF
Model size
4 params
Architecture
Hardware compatibility
Log In to add your hardware

We're not able to determine the quantization variants.

Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support