diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 4d5a14ef67..06ac6fa845 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3566,7 +3566,7 @@ static linenr_T dump_prefixes(slang_T *slang, char *word, char *pat, Direction * } } } - } else { + } else if (depth < MAXWLEN - 1) { // Normal char, go one level deeper. prefix[depth++] = (char)c; arridx[depth] = idxs[n]; diff --git a/test/old/testdir/test_spell.vim b/test/old/testdir/test_spell.vim index 6b4c2e7c4b..5d5d815199 100644 --- a/test/old/testdir/test_spell.vim +++ b/test/old/testdir/test_spell.vim @@ -1592,4 +1592,31 @@ func Test_suggest_spell_restore() bwipe! endfunc +" A crafted .spl with a self-referential BY_INDEX node in the PREFIXTREE drove +" dump_prefixes() past its MAXWLEN-sized depth arrays (stack out-of-bounds +" write). The tree parses cleanly (shared refs aren't recursed); the walk +" happens on :spelldump. Reaching the assert means no OOB. Same class as the +" tree_count_words() fix (9.2.0653). +func Test_spelldump_prefixtree_overflow() + CheckUnix + call mkdir('Xrtp/spell', 'pR') + " VIMspell + v50, SN_PREFCOND(prefixcnt=1), SN_END, + " LWORDTREE word "a" with affixID=1 (so dump_prefixes runs), + " empty KWORDTREE, PREFIXTREE child BY_INDEX -> nodeidx 0 (self-cycle), 'A' + let spl = eval('0z56494D7370656C6C32030000000003000100FF00000004' + \ .. '0161010220010000000000000002010100000041') + call writefile(spl, 'Xrtp/spell/xx.utf-8.spl', 'b') + + new + set runtimepath+=./Xrtp + set spelllang=xx + set spell + spelldump + call assert_true(line('$') > 1) + + set spell& spelllang& runtimepath& + bwipe! + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab