]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0725: [security]: Stack out-of-bounds write in spell_soundfold_sal() v9.2.0725
authorHirohito Higashi <h.east.727@gmail.com>
Mon, 22 Jun 2026 04:00:36 +0000 (13:00 +0900)
committerChristian Brabandt <cb@256bit.org>
Wed, 24 Jun 2026 20:15:29 +0000 (20:15 +0000)
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](https://github.com/vim/vim/security/advisories/GHSA-q8mh-6qm3-25g4)
(9.2.0698)

Github Security Advisory:
https://github.com/vim/vim/security/advisories/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>
src/spell.c
src/testdir/test_spellfile.vim
src/version.c

index 96700782ef222938f075d3b5d2cb2b73ee94b78e..12766042517d5dd12c47f6bcf490765c102f2f75 100644 (file)
@@ -3513,7 +3513,7 @@ spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res)
                        // no '<' rule used
                        i += k - 1;
                        z = 0;
-                       while (*s != NUL && s[1] != NUL && reslen < MAXWLEN)
+                       while (*s != NUL && s[1] != NUL && reslen < MAXWLEN - 1)
                        {
                            if (reslen == 0 || res[reslen - 1] != *s)
                                res[reslen++] = *s;
@@ -3523,7 +3523,7 @@ spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res)
                        c = *s;
                        if (strstr((char *)pf, "^^") != NULL)
                        {
-                           if (c != NUL)
+                           if (c != NUL && reslen < MAXWLEN - 1)
                                res[reslen++] = c;
                            STRMOVE(word, word + i + 1);
                            i = 0;
@@ -3542,7 +3542,7 @@ spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res)
 
        if (z0 == 0)
        {
-           if (k && !p0 && reslen < MAXWLEN && c != NUL
+           if (k && !p0 && reslen < MAXWLEN - 1 && c != NUL
                    && (!slang->sl_collapse || reslen == 0
                                                     || res[reslen - 1] != c))
                // condense only double letters
index 9ec728a768a4ee94268c2a3ec523609b16a6f809..951538d514add2bec937f3efeb153f2df553a330 100644 (file)
@@ -405,6 +405,30 @@ func Test_spellfile_format_error()
   let &rtp = save_rtp
 endfunc
 
+" An over-length soundfold() argument must not overflow the MAXWLEN result
+" buffer in the single-byte branch of spell_soundfold_sal().
+func Test_spellfile_soundfold_sal_overflow()
+  let save_enc = &encoding
+  set encoding=latin1
+  " A SAL map that appends without collapsing, so the result is not shorter
+  " than the input.
+  call writefile(['SET ISO8859-1', 'SAL collapse_result false',
+       \ 'SAL a aaaa', 'SAL b bbbb'], 'Xsal.aff')
+  call writefile(['2', 'hello', 'world'], 'Xsal.dic')
+  mkspell! Xsal Xsal
+  set spl=Xsal.latin1.spl spell
+
+  " 253 input characters hit the buffer boundary; the result must not exceed
+  " MAXWLEN - 1.
+  call assert_true(strlen(soundfold(repeat('a', 253))) <= 253)
+
+  set nospell spl& spelllang&
+  call delete('Xsal.aff')
+  call delete('Xsal.dic')
+  call delete('Xsal.latin1.spl')
+  let &encoding = save_enc
+endfunc
+
 " Test for format errors in suggest file
 func Test_sugfile_format_error()
   let save_rtp = &rtp
index 010537fa1e5c214648fdc3b7443848332a67c67d..ed23c9f597ada34db022d0a18fcc6c947705e1f8 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    725,
 /**/
     724,
 /**/