Summary
There is a use of uninitialized heap variable vulnerability in gguf_init_from_file, the code will free this uninitialized variable later. In a simple POC, it will directly cause a crash. If the file is carefully constructed, it may be possible to control this uninitialized value and cause arbitrary address free problems. This may further lead to be exploited.
Details
In switch case kv->type == GGUF_TYPE_ARRAY, it will malloc an array of heap chunks to store the data, and the pointers will be stored at kv->value.arr.data
, like GGUF_TYPE_STRING:
case GGUF_TYPE_STRING:
{
// prevent from integer overflow in the malloc below
if (kv->value.arr.n >= SIZE_MAX/sizeof(struct gguf_str)) {
fprintf(stderr, "%s: array size is too large (%" PRIu64 ")\n", __func__, kv->value.arr.n);
fclose(file);
gguf_free(ctx);
return NULL;
}
kv->value.arr.data = GGML_MALLOC(kv->value.arr.n * sizeof(struct gguf_str));
for (uint64_t j = 0; j < kv->value.arr.n; ++j) {
ok = ok && gguf_fread_str(file, &((struct gguf_str *) kv->value.arr.data)[j], &offset);
}
} break;
in gguf_fread_str
, the code may return directly without executing malloc and storing pointer.
static bool gguf_fread_str(FILE * file, struct gguf_str * p, size_t * offset) {
p->n = 0;
p->data = NULL;
bool ok = true;
ok = ok && gguf_fread_el(file, &p->n, sizeof(p->n), offset);
// early exit if string length is invalid, prevents from integer overflow
if (p->n == SIZE_MAX) {
fprintf(stderr, "%s: invalid string length (%" PRIu64 ")\n", __func__, p->n);
return false;
}
p->data = GGML_CALLOC(p->n + 1, 1);
ok = ok && gguf_fread_el(file, p->data, p->n, offset);
return ok;
}
Then when the program executes to any gguf_free(ctx);
, it will cause a free of this uninitialized variable.
if (kv->type == GGUF_TYPE_ARRAY) {
if (kv->value.arr.data) {
if (kv->value.arr.type == GGUF_TYPE_STRING) {
for (uint64_t j = 0; j < kv->value.arr.n; ++j) {
struct gguf_str * str = &((struct gguf_str *) kv->value.arr.data)[j];
if (str->data) {
GGML_FREE(str->data);
}
}
}
GGML_FREE(kv->value.arr.data);
}
}
ASAN Log
➜ llama.cpp git:(master) ✗ ./main -m seeds_dir/obsidian-q6.gguf1 -p "hello" -n 400 -e
Log start
main: build = 2277 (cbbd1efa)
main: built with cc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 for x86_64-linux-gnu
main: seed = 1713891244
gguf_init_from_file: failed to read key-value pairs
AddressSanitizer:DEADLYSIGNAL
=================================================================
==20337==ERROR: AddressSanitizer: BUS on unknown address 0x000000000000 (pc 0x7f963e379a16 bp 0xbebebebebebebeae sp 0x7ffe29c7a820 T0)
#0 0x7f963e379a15 in bool __sanitizer::atomic_compare_exchange_strong<__sanitizer::atomic_uint8_t>(__sanitizer::atomic_uint8_t volatile*, __sanitizer::atomic_uint8_t::Type*, __sanitizer::atomic_uint8_t::Type, __sanitizer::memory_order) ../../../../src/libsanitizer/sanitizer_common/sanitizer_atomic_clang.h:79
#1 0x7f963e379a15 in __asan::Allocator::AtomicallySetQuarantineFlagIfAllocated(__asan::AsanChunk*, void*, __sanitizer::BufferedStackTrace*) ../../../../src/libsanitizer/asan/asan_allocator.cc:552
#2 0x7f963e379a15 in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType) ../../../../src/libsanitizer/asan/asan_allocator.cc:629
#3 0x7f963e379a15 in __asan::asan_free(void*, __sanitizer::BufferedStackTrace*, __asan::AllocType) ../../../../src/libsanitizer/asan/asan_allocator.cc:865
#4 0x7f963e45e3d8 in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:127
#5 0x5573c99f4067 in gguf_free (/home/abc/llama.cpp/main+0x13c067)
#6 0x5573c99f5827 in gguf_init_from_file (/home/abc/llama.cpp/main+0x13d827)
#7 0x5573c9bb465c in llama_model_loader::llama_model_loader(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool, llama_model_kv_override const*) (/home/abc/llama.cpp/main+0x2fc65c)
#8 0x5573c9b0e354 in llama_model_load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, llama_model&, llama_model_params&) (/home/abc/llama.cpp/main+0x256354)
#9 0x5573c9b11cfb in llama_load_model_from_file (/home/abc/llama.cpp/main+0x259cfb)
#10 0x5573c9bd825c in llama_init_from_gpt_params(gpt_params&) (/home/abc/llama.cpp/main+0x32025c)
#11 0x5573c9941dd0 in main (/home/abc/llama.cpp/main+0x89dd0)
#12 0x7f963de14082 in __libc_start_main ../csu/libc-start.c:308
#13 0x5573c995a2cd in _start (/home/abc/llama.cpp/main+0xa22cd)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: BUS ../../../../src/libsanitizer/sanitizer_common/sanitizer_atomic_clang.h:79 in bool __sanitizer::atomic_compare_exchange_strong<__sanitizer::atomic_uint8_t>(__sanitizer::atomic_uint8_t volatile*, __sanitizer::atomic_uint8_t::Type*, __sanitizer::atomic_uint8_t::Type, __sanitizer::memory_order)
==20337==ABORTING
PoC
https://drive.google.com/file/d/1ym_GlXSI3edGVtHzTK-LTcJScSPMoauX/view?usp=sharing
Impact
Causes llama.cpp to crash (DoS) and may even lead to arbitrary code execution (RCE).
Summary
There is a use of uninitialized heap variable vulnerability in gguf_init_from_file, the code will free this uninitialized variable later. In a simple POC, it will directly cause a crash. If the file is carefully constructed, it may be possible to control this uninitialized value and cause arbitrary address free problems. This may further lead to be exploited.
Details
In switch case kv->type == GGUF_TYPE_ARRAY, it will malloc an array of heap chunks to store the data, and the pointers will be stored at
kv->value.arr.data
, like GGUF_TYPE_STRING:in
gguf_fread_str
, the code may return directly without executing malloc and storing pointer.Then when the program executes to any
gguf_free(ctx);
, it will cause a free of this uninitialized variable.ASAN Log
PoC
https://drive.google.com/file/d/1ym_GlXSI3edGVtHzTK-LTcJScSPMoauX/view?usp=sharing
Impact
Causes llama.cpp to crash (DoS) and may even lead to arbitrary code execution (RCE).