Skip to content

Use of Uninitialized Variable Vulnerability in gguf_init_from_file

High
ggerganov published GHSA-p5mv-gjc5-mwqv Apr 26, 2024

Package

llama.cpp

Affected versions

b2715

Patched versions

b2740

Description

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

Snipaste_2024-04-24_00-42-34

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).

Severity

High
7.1
/ 10

CVSS base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:L

CVE ID

CVE-2024-32878

Weaknesses

No CWEs

Credits