From 4f7b6083e5fe13ae0474520c769e1d3aeb25d393 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 11 Apr 2026 07:39:54 +0800 Subject: [PATCH] vim-patch:9.2.0331: spellfile: stack buffer overflows in spell file generation (#38948) Problem: spell_read_aff() uses sprintf() into a fixed-size stack buffer without bounds checking. store_aff_word() uses STRCAT() to append attacker-controlled strings into newword[MAXWLEN] without checking remaining space. Both are reachable via :mkspell with crafted .aff/.dic files (xinyi234) Solution: Replace sprintf() with vim_snprintf() in spell_read_aff(). Replace STRCAT() with STRNCAT() with explicit remaining-space calculation in store_aff_word(). closes: vim/vim#19944 https://github.com/vim/vim/commit/07faa961a05bc5ea007ab70ff483ea1b32c3371d Co-authored-by: Christian Brabandt --- src/nvim/spellfile.c | 10 ++++++++-- test/old/testdir/test_spellfile.vim | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 20f4140a65..ab4f16eb3e 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -2437,6 +2437,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname) char buf[MAXLINELEN]; aff_entry->ae_cond = getroom_save(spin, items[4]); + // Note: this silently truncates the buffer, but this should + // not happen in practice snprintf(buf, sizeof(buf), *items[0] == 'P' ? "^%s" : "%s$", items[4]); aff_entry->ae_prog = vim_regcomp(buf, RE_MAGIC + RE_STRING + RE_STRICT); if (aff_entry->ae_prog == NULL) { @@ -3391,7 +3393,9 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_ MB_PTR_ADV(p); } } - strcat(newword, p); + // Note: this silently truncates the buffer, but this should + // not happen in practice + xstrlcat(newword, p, MAXWLEN); } else { // suffix: chop/add at the end of the word xstrlcpy(newword, word, MAXWLEN); @@ -3405,7 +3409,9 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_ *p = NUL; } if (ae->ae_add != NULL) { - strcat(newword, ae->ae_add); + // Note: this silently truncates the buffer, but this should + // not happen in practice + xstrlcat(newword, ae->ae_add, MAXWLEN); } } diff --git a/test/old/testdir/test_spellfile.vim b/test/old/testdir/test_spellfile.vim index 351ffc9612..02878877b1 100644 --- a/test/old/testdir/test_spellfile.vim +++ b/test/old/testdir/test_spellfile.vim @@ -1170,5 +1170,32 @@ func Test_mkspell_empty_dic() call delete('XtestEmpty.spl') endfunc +" This used to cause a buffer overflow +func Test_mkspell_no_buffer_overflow() + CheckNotMSWindows + + let aff_lines = ['SET ISO8859-1', 'SFX A Y 1', + \ 'SFX A 0 s ' .. repeat(nr2char(0xff), 491)] + call writefile(aff_lines, 'Xbof.aff', 'D') + call writefile(['1', 'word/A'], 'Xbof.dic', 'D') + " Must not crash; ignore any conversion/regex errors. + try + mkspell! Xbof.spl Xbof + catch + endtry + defer delete('Xbof.spl') + + let long = repeat(nr2char(0xff), 200) + let aff2_lines = ['SET ISO8859-1', 'SFX A Y 1', + \ 'SFX A 0 ' .. long .. ' .'] + call writefile(aff2_lines, 'Xbof2.aff', 'D') + call writefile(['1', long .. '/A'], 'Xbof2.dic', 'D') + try + mkspell! Xbof2.spl Xbof2 + catch + endtry + defer delete('Xbof2.spl') +endfunc + " vim: shiftwidth=2 sts=2 expandtab