Skip to content

Undefined behavior in whisper.cpp model loading: null pointer passed to memcpy + signed integer overflow #3879

Description

@professor-moody

Summary

Two UBSan-detected undefined-behavior bugs occur in whisper_model_load() when parsing a crafted
GGUF model file. The first passes a null pointer to memcpy in the buffer loader's read callback;
the second is a signed integer overflow when computing the mel filter buffer size from unvalidated
header fields. Both are undefined behavior under the C/C++ standards; the practical impact (whether
either becomes a hard crash) is platform- and build-dependent and is not a confirmed SEGV.

Affected component

  • src/whisper.cppwhisper_model_load()
    • Bug 1: null pointer passed to memcpy at src/whisper.cpp:3688
    • Bug 2: signed integer overflow at src/whisper.cpp:1583

Reproduction

PoCs:

  • poc-051-whisper-null-ptr.bin (68 bytes) — triggers the null-pointer-to-memcpy UB
  • poc-051-whisper-int-overflow.bin (52 bytes) — triggers the signed integer overflow

Load either file through the buffer-based model loader (e.g.
whisper_init_from_buffer_with_params). On a UBSan-instrumented build:

src/whisper.cpp:3688:16: runtime error: null pointer passed as argument 1, which is declared to never be null
src/whisper.cpp:1583:43: runtime error: signed integer overflow: 1920274432 * -1094795586 cannot be represented in type 'int32_t'

Root cause

Bug 1 — null pointer to memcpy (whisper.cpp:3688)

The custom buffer loader's read callback passes a null-derived source pointer to memcpy() when
the buffer context is exhausted or its base pointer is null. Passing a null source to memcpy is
undefined behavior regardless of the copy size:

loader.read = [](void * ctx, void * output, size_t read_size) {
    buf_context * buf = reinterpret_cast<buf_context *>(ctx);
    size_t size_to_copy = buf->current_offset + read_size < buf->size
        ? read_size : buf->size - buf->current_offset;
    memcpy(output, buf->buffer + buf->current_offset, size_to_copy);  // null source -> UB
    buf->current_offset += size_to_copy;
    return size_to_copy;
};

Bug 2 — signed integer overflow (whisper.cpp:1583)

filters.n_mel and filters.n_fft are read directly from the model file with no validation, and
their product is computed in int32_t before being passed to resize(). Crafted values overflow
the multiplication (undefined behavior); the subsequent resize() may throw std::length_error or
allocate a garbage-sized buffer:

read_safe(loader, filters.n_mel);   // attacker-controlled int32_t
read_safe(loader, filters.n_fft);   // attacker-controlled int32_t
filters.data.resize(filters.n_mel * filters.n_fft);   // int32_t overflow -> UB

Suggested fix

Bug 1 — guard the copy:

if (size_to_copy > 0 && buf->buffer != nullptr) {
    memcpy(output, buf->buffer + buf->current_offset, size_to_copy);
}

Bug 2 — validate the dimensions and widen the multiplication:

if (filters.n_mel <= 0 || filters.n_fft <= 0 ||
    filters.n_mel > 1024 || filters.n_fft > 32768) {
    WHISPER_LOG_ERROR("%s: invalid mel filter dimensions: n_mel=%d, n_fft=%d\n",
                      __func__, filters.n_mel, filters.n_fft);
    return false;
}
filters.data.resize(static_cast<size_t>(filters.n_mel) * filters.n_fft);

Revalidation

Revalidated live on 2026-06-14 against HEAD df7638d8 (UBSan build):

  • null pointer passed as argument 1 ... declared never null @ src/whisper.cpp:3688
  • signed integer overflow: 1920274432 * -1094795586 @ src/whisper.cpp:1583

Both are UBSan-detected undefined behavior. Hard-crash severity is platform-dependent: Bug 1's
impact depends on the copy size and the build's release behavior (not a confirmed SEGV); Bug 2 wraps
on a release build unless it drives a downstream out-of-bounds access. Frame as undefined behavior,
not as a confirmed crash. Precedent for a whisper.cpp UB-class CVE in this area: CVE-2026-10298.

Proof-of-concept files (base64)

Decode with base64 -d > file.

poc-051-whisper-null-ptr.bin (68 bytes):

bG1nZ0dHVUYDAABHAEcAAwAAIgAAAAAAAAAAAABVRgAAIgAAAAAAAAAAAAAAAAAAAAAAAAD///////////////8AAAA=

poc-051-whisper-int-overflow.bin (52 bytes):

bG1nZ0dHVUYDAAgAAAAAAAAAAAABAAAAAAAAAEYDAAAAAAAAAAAAAAABAAAAAAAAABB1cg==

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions