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; +}