From 1349233cd150dd904c38547371a24765d9217454 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 15 Feb 2026 00:13:37 +0000 Subject: [PATCH] fix(prompt): heap-buffer-overflow in prompt_setprompt Problem: prompt_setprompt may check the wrong buffer, which can lead to a heap-buffer-overflow. Solution: don't use curbuf. Also replace all kCallbackNone initializers with CALLBACK_INIT. --- src/nvim/eval/buffer.c | 13 +++++-------- src/nvim/ex_getln.c | 2 +- test/functional/legacy/prompt_buffer_spec.lua | 19 +++++++++++++++++-- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index ff91c39c34..117acdcf5e 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -712,7 +712,7 @@ void restore_buffer(bufref_T *save_curbuf) /// "prompt_setcallback({buffer}, {callback})" function void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - Callback prompt_callback = { .type = kCallbackNone }; + Callback prompt_callback = CALLBACK_INIT; if (check_secure()) { return; @@ -735,7 +735,7 @@ void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "prompt_setinterrupt({buffer}, {callback})" function void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - Callback interrupt_callback = { .type = kCallbackNone }; + Callback interrupt_callback = CALLBACK_INIT; if (check_secure()) { return; @@ -772,10 +772,8 @@ void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Update the prompt-text and prompt-marks if a plugin calls prompt_setprompt() // even while user is editing their input. if (bt_prompt(buf)) { - if (buf->b_prompt_start.mark.lnum > buf->b_ml.ml_line_count) { - // In case the mark is set to a nonexistent line. - buf->b_prompt_start.mark.lnum = buf->b_ml.ml_line_count; - } + // In case the mark is set to a nonexistent line. + buf->b_prompt_start.mark.lnum = MIN(buf->b_prompt_start.mark.lnum, buf->b_ml.ml_line_count); linenr_T prompt_lno = buf->b_prompt_start.mark.lnum; char *old_prompt = buf_prompt_text(buf); @@ -786,8 +784,7 @@ void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) colnr_T cursor_col = curwin->w_cursor.col; if (buf->b_prompt_start.mark.col < old_prompt_len - || curbuf->b_prompt_start.mark.col < old_prompt_len - || !strnequal(old_prompt, old_line + curbuf->b_prompt_start.mark.col - old_prompt_len, + || !strnequal(old_prompt, old_line + buf->b_prompt_start.mark.col - old_prompt_len, (size_t)old_prompt_len)) { // If for some odd reason the old prompt is missing, // replace prompt line with new-prompt (discards user-input). diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 790f0f7dec..08d01700d6 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4883,7 +4883,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const typval_T *cancelreturn = NULL; typval_T cancelreturn_strarg2 = TV_INITIAL_VALUE; const char *xp_name = NULL; - Callback input_callback = { .type = kCallbackNone }; + Callback input_callback = CALLBACK_INIT; char prompt_buf[NUMBUFLEN]; char defstr_buf[NUMBUFLEN]; char cancelreturn_buf[NUMBUFLEN]; diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index fa8d01e1fc..83825c8a94 100644 --- a/test/functional/legacy/prompt_buffer_spec.lua +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -798,8 +798,8 @@ describe('prompt buffer', function() api.nvim_set_option_value('buftype', 'prompt', { buf = 0 }) local buf = api.nvim_get_current_buf() - local function set_prompt(prompt) - fn('prompt_setprompt', buf, prompt) + local function set_prompt(prompt, b) + fn('prompt_setprompt', b or buf, prompt) end set_prompt('> ') @@ -846,5 +846,20 @@ describe('prompt buffer', function() {5:-- INSERT --} | ]]) eq({ 1, 13 }, api.nvim_buf_get_mark(0, ':')) + + -- No crash when setting shorter prompt than curbuf's in other buffer. + command('new | setlocal buftype=prompt') + set_prompt('looooooooooooooooooooooooooooooooooooooooooooong > ', '') -- curbuf + set_prompt('foo > ') + screen:expect([[ + loooooooooooooooooooooooo| + ooooooooooooooooooooong >| + ^ | + {1:~ }| + {3:[Prompt] [+] }| + foo > user input | + {1:~ }|*3 + {5:-- INSERT --} | + ]]) end) end)