// Faithful isolation of the unchecked n_dims -> ne[2] stack overflow shared by koboldcpp's legacy // ggml loaders (otherarch/gpt2_v1/v2/v3, gptj_v1/v2/v3, mpt_v3, llama_v2/v3, neox_v2/v3, rwkv_v2). // e.g. gpt2_v2.cpp:269-273: // int32_t ne[2] = {1,1}; // for (int i = 0; i < n_dims; ++i) { fin.read(&ne[i], sizeof(ne[i])); nelements *= ne[i]; } // n_dims is read straight from the model file with no `n_dims<=2` check -> n_dims>=3 writes past the // 2-element stack array with fully attacker-controlled bytes -> stack smash (CWE-787). // Build: c++ -O2 -g -fstack-protector-all ndims_repro.cpp -o ndims_repro Run: ./ndims_repro 100000 #include #include #include int main(int argc, char** argv){ int32_t n_dims = argc>1 ? atoi(argv[1]) : 100000; // attacker file field, unbounded int32_t nelements = 1; int32_t ne[2] = { 1, 1 }; // fixed 2-element stack array fprintf(stderr, "n_dims=%d writing past int32_t ne[2] ...\n", n_dims); fflush(stderr); for (int i = 0; i < n_dims; ++i) { // no bound check ne[i] = 0x41414141; // = fin.read() of attacker bytes nelements *= ne[i]; } fprintf(stderr, "survived (unexpected) %d\n", nelements); return 0; }