summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/ex_cmds.c50
-rw-r--r--src/testdir/test_substitute.vim48
-rw-r--r--src/version.c2
3 files changed, 85 insertions, 15 deletions
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index c5f912e7ee..a08682b071 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -3737,13 +3737,13 @@ ex_substitute(exarg_T *eap)
int save_do_all; // remember user specified 'g' flag
int save_do_ask; // remember user specified 'c' flag
char_u *pat = NULL, *sub = NULL; // init for GCC
- char_u *sub_copy = NULL;
int delimiter;
int sublen;
int got_quit = FALSE;
int got_match = FALSE;
int which_pat;
char_u *cmd;
+ char_u *p;
int save_State;
linenr_T first_line = 0; // first changed line
linenr_T last_line= 0; // below last changed line AFTER the
@@ -3827,8 +3827,12 @@ ex_substitute(exarg_T *eap)
* Small incompatibility: vi sees '\n' as end of the command, but in
* Vim we want to use '\n' to find/substitute a NUL.
*/
- sub = cmd; // remember the start of the substitution
+ p = cmd; // remember the start of the substitution
cmd = skip_substitute(cmd, delimiter);
+ sub = vim_strsave(p);
+ if (sub == NULL)
+ // out of memory
+ return;
if (!eap->skip)
{
@@ -3839,14 +3843,22 @@ ex_substitute(exarg_T *eap)
if (old_sub == NULL) // there is no previous command
{
emsg(_(e_no_previous_substitute_regular_expression));
+ vim_free(sub);
return;
}
- sub = old_sub;
+ vim_free(sub);
+ sub = vim_strsave(old_sub);
+ if (sub == NULL)
+ // out of memory
+ return;
}
else
{
vim_free(old_sub);
old_sub = vim_strsave(sub);
+ if (old_sub == NULL)
+ // out of memory
+ return;
}
}
}
@@ -3858,7 +3870,7 @@ ex_substitute(exarg_T *eap)
return;
}
pat = NULL; // search_regcomp() will use previous pattern
- sub = old_sub;
+ sub = vim_strsave(old_sub);
// Vi compatibility quirk: repeating with ":s" keeps the cursor in the
// last column after using "$".
@@ -3877,7 +3889,10 @@ ex_substitute(exarg_T *eap)
linenr_T joined_lines_count;
if (eap->skip)
+ {
+ vim_free(sub);
return;
+ }
curwin->w_cursor.lnum = eap->line1;
if (*cmd == 'l')
eap->flags = EXFLAG_LIST;
@@ -3904,6 +3919,7 @@ ex_substitute(exarg_T *eap)
save_re_pat(RE_SUBST, pat, magic_isset());
// put pattern in history
add_to_history(HIST_SEARCH, pat, TRUE, NUL);
+ vim_free(sub);
return;
}
@@ -3991,6 +4007,7 @@ ex_substitute(exarg_T *eap)
if (i <= 0 && !eap->skip && subflags.do_error)
{
emsg(_(e_positive_count_required));
+ vim_free(sub);
return;
}
else if (i >= INT_MAX)
@@ -3998,6 +4015,7 @@ ex_substitute(exarg_T *eap)
char buf[20];
vim_snprintf(buf, sizeof(buf), "%ld", i);
semsg(_(e_val_too_large), buf);
+ vim_free(sub);
return;
}
eap->line1 = eap->line2;
@@ -4016,17 +4034,22 @@ ex_substitute(exarg_T *eap)
if (eap->nextcmd == NULL)
{
semsg(_(e_trailing_characters_str), cmd);
+ vim_free(sub);
return;
}
}
if (eap->skip) // not executing commands, only parsing
+ {
+ vim_free(sub);
return;
+ }
if (!subflags.do_count && !curbuf->b_p_ma)
{
// Substitution is not allowed in non-'modifiable' buffer
emsg(_(e_cannot_make_changes_modifiable_is_off));
+ vim_free(sub);
return;
}
@@ -4034,6 +4057,7 @@ ex_substitute(exarg_T *eap)
{
if (subflags.do_error)
emsg(_(e_invalid_command));
+ vim_free(sub);
return;
}
@@ -4054,20 +4078,20 @@ ex_substitute(exarg_T *eap)
*/
if (sub[0] == '\\' && sub[1] == '=')
{
- sub = vim_strsave(sub);
- if (sub == NULL)
+ p = vim_strsave(sub);
+ vim_free(sub);
+ if (p == NULL)
return;
- sub_copy = sub;
+ sub = p;
}
else
{
- char_u *newsub = regtilde(sub, magic_isset());
+ p = regtilde(sub, magic_isset());
- if (newsub != sub)
+ if (p != sub)
{
- // newsub was allocated, free it later.
- sub_copy = newsub;
- sub = newsub;
+ vim_free(sub);
+ sub = p;
}
}
@@ -4965,7 +4989,7 @@ outofmem:
#endif
vim_regfree(regmatch.regprog);
- vim_free(sub_copy);
+ vim_free(sub);
// Restore the flag values, they can be used for ":&&".
subflags.do_all = save_do_all;
diff --git a/src/testdir/test_substitute.vim b/src/testdir/test_substitute.vim
index 3ed159799f..7c2bbb4767 100644
--- a/src/testdir/test_substitute.vim
+++ b/src/testdir/test_substitute.vim
@@ -4,6 +4,32 @@ source shared.vim
source check.vim
source screendump.vim
+" NOTE: This needs to be the first test to be
+" run in the file, since it depends on
+" that the previous substitution atom
+" was not yet set.
+"
+" recursive call of :s and sub-replace special
+" (did cause heap-use-after free in < v9.0.2121)
+func Test_aaaa_substitute_expr_recursive_special()
+ func R()
+ " FIXME: leaving out the 'n' flag leaks memory, why?
+ %s/./\='.'/gn
+ endfunc
+ new Xfoobar_UAF
+ put ='abcdef'
+ let bufnr = bufnr('%')
+ try
+ silent! :s/./~\=R()/0
+ "call assert_fails(':s/./~\=R()/0', 'E939:')
+ let @/='.'
+ ~g
+ catch /^Vim\%((\a\+)\)\=:E565:/
+ endtry
+ delfunc R
+ exe bufnr .. "bw!"
+endfunc
+
func Test_multiline_subst()
enew!
call append(0, ["1 aa",
@@ -147,7 +173,6 @@ func Test_substitute_repeat()
call feedkeys("Qsc\<CR>y", 'tx')
bwipe!
endfunc
-
" Test %s/\n// which is implemented as a special case to use a
" more efficient join rather than doing a regular substitution.
func Test_substitute_join()
@@ -1447,11 +1472,30 @@ func Test_substitute_expr_switch_win()
endfunc
new Xfoobar
let bufnr = bufnr('%')
- put ="abcdef"
+ put ='abcdef'
silent! s/\%')/\=R()
call assert_fails(':%s/./\=R()/g', 'E565:')
delfunc R
exe bufnr .. "bw!"
endfunc
+" recursive call of :s using test-replace special
+func Test_substitute_expr_recursive()
+ func Q()
+ %s/./\='foobar'/gn
+ return "foobar"
+ endfunc
+ func R()
+ %s/./\=Q()/g
+ endfunc
+ new Xfoobar_UAF
+ let bufnr = bufnr('%')
+ put ='abcdef'
+ silent! s/./\=R()/g
+ call assert_fails(':%s/./\=R()/g', 'E565:')
+ delfunc R
+ delfunc Q
+ exe bufnr .. "bw!"
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index edf6b576fe..642da01d8b 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 2121,
+/**/
2120,
/**/
2119,