From 6c6e74c300e87d0b2fb7a9e511f52bed460fbccf Mon Sep 17 00:00:00 2001 From: Ben Younes <2910651+ousamabenyounes@users.noreply.github.com> Date: Sun, 9 Aug 2026 21:46:28 +0000 Subject: [PATCH] whisper : guard null source in buffer loader read callback whisper_init_from_buffer_with_params_no_state installs a read callback that copies from buf->buffer + current_offset. When the buffer is exhausted (or the supplied buffer is empty), size_to_copy is 0 and the source pointer can be null; passing a null pointer to memcpy is undefined behavior even for a zero-length copy (UBSan: 'null pointer passed as argument 2' at the memcpy). Loading a crafted/short model through the buffer loader could hit this. Skip the memcpy when there is nothing to copy. Loading from a null/empty or truncated buffer now fails gracefully (returns NULL) with no UB. This addresses bug 1 of #3879. Bug 2 (integer overflow when sizing the mel filter buffer) is covered by the open PR #3780. --- src/whisper.cpp | 7 +++++- tests/CMakeLists.txt | 8 +++++++ tests/test-whisper-buffer-loader.cpp | 33 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 tests/test-whisper-buffer-loader.cpp diff --git a/src/whisper.cpp b/src/whisper.cpp index 89146e2e4..e81f68285 100644 --- a/src/whisper.cpp +++ b/src/whisper.cpp @@ -3695,7 +3695,12 @@ struct whisper_context * whisper_init_from_buffer_with_params_no_state(void * bu 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); + // When the buffer is exhausted size_to_copy is 0 and buf->buffer may be + // null (empty buffer); passing a null source to memcpy is undefined + // behavior even for a zero-length copy, so skip it (issue #3879). + if (size_to_copy > 0) { + memcpy(output, buf->buffer + buf->current_offset, size_to_copy); + } buf->current_offset += size_to_copy; return size_to_copy; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aecc6f3b2..fe5d5202d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -96,6 +96,14 @@ target_link_libraries(${UTF8_TEST} PRIVATE common) add_test(NAME ${UTF8_TEST} COMMAND ${UTF8_TEST}) set_tests_properties(${UTF8_TEST} PROPERTIES LABELS "unit") +# Loading from a null/empty or truncated buffer must fail gracefully (#3879) +set(BUFFER_LOADER_TEST test-whisper-buffer-loader) +add_executable(${BUFFER_LOADER_TEST} ${BUFFER_LOADER_TEST}.cpp) +target_include_directories(${BUFFER_LOADER_TEST} PRIVATE ../include ../ggml/include ../examples) +target_link_libraries(${BUFFER_LOADER_TEST} PRIVATE common) +add_test(NAME ${BUFFER_LOADER_TEST} COMMAND ${BUFFER_LOADER_TEST}) +set_tests_properties(${BUFFER_LOADER_TEST} PROPERTIES LABELS "unit;gh") + # VAD test tests VAD in isolation set(VAD_TEST test-vad) add_executable(${VAD_TEST} ${VAD_TEST}.cpp) diff --git a/tests/test-whisper-buffer-loader.cpp b/tests/test-whisper-buffer-loader.cpp new file mode 100644 index 000000000..b72e8f57f --- /dev/null +++ b/tests/test-whisper-buffer-loader.cpp @@ -0,0 +1,33 @@ +// Regression test for issue #3879 (bug 1): +// The buffer-based model loader's read callback passed buf->buffer + offset to +// memcpy even when nothing was left to copy. For an empty buffer that source is +// NULL, and passing a NULL pointer to memcpy is undefined behavior even for a +// zero-length copy. Loading from a null/empty or truncated buffer must fail +// gracefully (return NULL) without invoking that UB. + +#include "whisper.h" + +#include +#include + +#ifdef NDEBUG +#undef NDEBUG +#endif +#include + +int main() { + struct whisper_context_params cparams = whisper_context_default_params(); + cparams.use_gpu = false; + + // Empty buffer: the read callback is driven with a NULL source pointer. + struct whisper_context * ctx_empty = whisper_init_from_buffer_with_params(nullptr, 0, cparams); + assert(ctx_empty == nullptr); + + // Truncated, non-model buffer: the loader runs out of bytes mid-read. + uint8_t truncated[8] = { 0 }; + struct whisper_context * ctx_trunc = whisper_init_from_buffer_with_params(truncated, sizeof(truncated), cparams); + assert(ctx_trunc == nullptr); + + printf("test-whisper-buffer-loader: OK\n"); + return 0; +}