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