Skip to content

Commit d22ff1c

Browse files
h-eastclaude
authored andcommitted
patch 9.2.0725: [security]: Stack out-of-bounds write in spell_soundfold_sal()
Problem: [security]: A crafted spell file with non-collapsing SAL rules can make soundfold() write one byte past the end of the MAXWLEN result buffer. This is the same class of out-of-bounds write as GHSA-q8mh-6qm3-25g4 (fixed in 9.2.0698 for the SOFO branch), found while auditing the surrounding code. Solution: Bound the single-byte SAL result writes and the terminating NUL to MAXWLEN - 1, matching the SOFO branch. The single-byte branch of spell_soundfold_sal() guarded its writes with "reslen < MAXWLEN", allowing reslen to reach MAXWLEN (254). The trailing "res[reslen] = NUL" then wrote at index 254 of the 254-byte stack buffer res[MAXWLEN], an off-by-one out-of-bounds write. Input is case-folded to about 253 characters, so a 253-character argument together with a SAL map that does not collapse (collapse_result false) reaches the boundary. Related to previous issue [GHSA-q8mh-6qm3-25g4](GHSA-q8mh-6qm3-25g4) (9.2.0698) Github Security Advisory: GHSA-m3hf-xcm3-xhm2 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Hirohito Higashi <h.east.727@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent 1ccf338 commit d22ff1c

3 files changed

Lines changed: 29 additions & 3 deletions

File tree

src/spell.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3513,7 +3513,7 @@ spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res)
35133513
// no '<' rule used
35143514
i += k - 1;
35153515
z = 0;
3516-
while (*s != NUL && s[1] != NUL && reslen < MAXWLEN)
3516+
while (*s != NUL && s[1] != NUL && reslen < MAXWLEN - 1)
35173517
{
35183518
if (reslen == 0 || res[reslen - 1] != *s)
35193519
res[reslen++] = *s;
@@ -3523,7 +3523,7 @@ spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res)
35233523
c = *s;
35243524
if (strstr((char *)pf, "^^") != NULL)
35253525
{
3526-
if (c != NUL)
3526+
if (c != NUL && reslen < MAXWLEN - 1)
35273527
res[reslen++] = c;
35283528
STRMOVE(word, word + i + 1);
35293529
i = 0;
@@ -3542,7 +3542,7 @@ spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res)
35423542

35433543
if (z0 == 0)
35443544
{
3545-
if (k && !p0 && reslen < MAXWLEN && c != NUL
3545+
if (k && !p0 && reslen < MAXWLEN - 1 && c != NUL
35463546
&& (!slang->sl_collapse || reslen == 0
35473547
|| res[reslen - 1] != c))
35483548
// condense only double letters

src/testdir/test_spellfile.vim

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,30 @@ func Test_spellfile_format_error()
405405
let &rtp = save_rtp
406406
endfunc
407407

408+
" An over-length soundfold() argument must not overflow the MAXWLEN result
409+
" buffer in the single-byte branch of spell_soundfold_sal().
410+
func Test_spellfile_soundfold_sal_overflow()
411+
let save_enc = &encoding
412+
set encoding=latin1
413+
" A SAL map that appends without collapsing, so the result is not shorter
414+
" than the input.
415+
call writefile(['SET ISO8859-1', 'SAL collapse_result false',
416+
\ 'SAL a aaaa', 'SAL b bbbb'], 'Xsal.aff')
417+
call writefile(['2', 'hello', 'world'], 'Xsal.dic')
418+
mkspell! Xsal Xsal
419+
set spl=Xsal.latin1.spl spell
420+
421+
" 253 input characters hit the buffer boundary; the result must not exceed
422+
" MAXWLEN - 1.
423+
call assert_true(strlen(soundfold(repeat('a', 253))) <= 253)
424+
425+
set nospell spl& spelllang&
426+
call delete('Xsal.aff')
427+
call delete('Xsal.dic')
428+
call delete('Xsal.latin1.spl')
429+
let &encoding = save_enc
430+
endfunc
431+
408432
" Test for format errors in suggest file
409433
func Test_sugfile_format_error()
410434
let save_rtp = &rtp

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,8 @@ static char *(features[]) =
759759

760760
static int included_patches[] =
761761
{ /* Add new patch number below this line */
762+
/**/
763+
725,
762764
/**/
763765
724,
764766
/**/

0 commit comments

Comments
 (0)