CVE-2024-22667 (C, published 2024-02-05T08:15Z)
Description: value: Vim before 9.0.2142 has a stack-based buffer overflow because did_set_langmap in map.c calls sprintf to write to the error buffer that is passed down to the option callback functions.
Severity: nan (CVSS v3 base score: 7.8)
Weakness: CWE-787 \u2014 Out-of-bounds Write
The product writes data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/vim/vim
Files changed:
src/map.c
src/option.c
src/option.h
src/optionstr.c
src/proto/optionstr.pro
src/structs.h
src/testdir/crash/poc_did_set_langmap
src/testdir/test_crash.vim
src/version.c
Commit message: patch 9.0.2142: [security]: stack-buffer-overflow in option callback functions

Problem:  [security]: stack-buffer-overflow in option callback functions
Solution: pass size of errbuf down the call stack, use snprintf()
          instead of sprintf()

We pass the error buffer down to the option callback functions, but in
some parts of the code, we simply use sprintf(buf) to write into the error
buffer, which can overflow.

So let's pass down the length of the error buffer and use sprintf(buf, size)
instead.

Reported by @henices, thanks!

Signed-off-by: Christian Brabandt <cb@256bit.org>


Vulnerable code:
			    sprintf(args->os_errbuf,
			opt_flags, op, value_checked);
					flags, cp_val, varp, errbuf,
	    char	errbuf[80];
					sizeof(errbuf));
    static char	errbuf[80];
	    return set_string_option(opt_idx, string, opt_flags, errbuf);
illegal_char(char *errbuf, int c)
    sprintf((char *)errbuf, _(e_illegal_character_str), (char *)transchar(c));
    char	*errbuf)
		    opt_flags, OP_NONE, &value_checked)) == NULL)
    static char errbuf[80];
	    return illegal_char(errbuf, *s);
		return illegal_char(errbuf, '}');
did_set_option_listflag(char_u *val, char_u *flags, char *errbuf)
	    return illegal_char(errbuf, *s);
		errmsg = illegal_char(args->os_errbuf, *s);
	    return illegal_char(args->os_errbuf, *s);
		    sprintf((char *)args->os_errbuf,
    return did_set_option_listflag(*varp, (char_u *)COCU_ALL, args->os_errbuf);
    return did_set_option_listflag(*varp, (char_u *)CPO_ALL, args->os_errbuf);
    return did_set_option_listflag(*varp, (char_u *)FO_ALL, args->os_errbuf);
    errmsg = did_set_option_listflag(*varp, (char_u *)GO_ALL, args->os_errbuf);
    return did_set_option_listflag(*varp, (char_u *)MOUSE_ALL,
							args->os_errbuf);
    return did_set_option_listflag(*varp, (char_u *)SHM_ALL, args->os_errbuf);
	    errmsg = illegal_char(args->os_errbuf, *s);
		    sprintf(args->os_errbuf,
    return did_set_option_listflag(*varp, (char_u *)(WW_ALL ","), args->os_errbuf);
char *set_string_option(int opt_idx, char_u *value, int opt_flags, char *errbuf);
char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, char_u *value, char *errbuf, int opt_flags, set_op_T op, int *value_checked);

Fixed code:
			    snprintf(args->os_errbuf, args->os_errbuflen,
	int	    errbuflen,
			errbuflen, opt_flags, op, value_checked);
					flags, cp_val, varp, errbuf, errbuflen,
	    char	errbuf[ERR_BUFLEN];
					ERR_BUFLEN);
    static char	errbuf[ERR_BUFLEN];
    int		errbuflen = ERR_BUFLEN;
	    return set_string_option(opt_idx, string, opt_flags, errbuf, errbuflen);
#define ERR_BUFLEN 80

illegal_char(char *errbuf, int errbuflen, int c)
    snprintf((char *)errbuf, errbuflen, _(e_illegal_character_str),
		    (char *)transchar(c));
    char	*errbuf,
    int		errbuflen)
		    errbuflen, opt_flags, OP_NONE, &value_checked)) == NULL)
    static char errbuf[ERR_BUFLEN];
    int		errbuflen = ERR_BUFLEN;
	    return illegal_char(errbuf, errbuflen, *s);
		return illegal_char(errbuf, errbuflen, '}');
did_set_option_listflag(
	char_u *val,
	char_u *flags,
	char *errbuf,
	int errbuflen)
	    return illegal_char(errbuf, errbuflen, *s);
		errmsg = illegal_char(args->os_errbuf, args->os_errbuflen, *s);
	    return illegal_char(args->os_errbuf, args->os_errbuflen, *s);
		    snprintf((char *)args->os_errbuf, args->os_errbuflen,
    return did_set_option_listflag(*varp, (char_u *)COCU_ALL, args->os_errbuf,
		    args->os_errbuflen);
    return did_set_option_listflag(*varp, (char_u *)CPO_ALL, args->os_errbuf,
		    args->os_errbuflen);
    return did_set_option_listflag(*varp, (char_u *)FO_ALL, args->os_errbuf,
		    args->os_errbuflen);
    errmsg = did_set_option_listflag(*varp, (char_u *)GO_ALL, args->os_errbuf,
		    args->os_errbuflen);
    return did_set_option_listflag(*varp, (char_u *)MOUSE_ALL, args->os_errbuf,
		    args->os_errbuflen);
    return did_set_option_listflag(*varp, (char_u *)SHM_ALL, args->os_errbuf,
		    args->os_errbuflen);
	    errmsg = illegal_char(args->os_errbuf, args->os_errbuflen, *s);
		    snprintf(args->os_errbuf, args->os_errbuflen,
    return did_set_option_listflag(*varp, (char_u *)(WW_ALL ","),
		    args->os_errbuf, args->os_errbuflen);
    int		errbuflen,		// length of error buffer
	args.os_errbuflen = errbuflen;
char *set_string_option(int opt_idx, char_u *value, int opt_flags, char *errbuf, int errbuflen);
char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, char_u *value, char *errbuf, int errbuflen, int opt_flags, set_op_T op, int *value_checked);
    // length of the error buffer
    int		os_errbuflen;
se lmap=°xÿ7sil;drlmap=°xÿ7sil;drmo: pm313"
  let file = 'crash/poc_did_set_langmap'
  let cmn_args = "%s -u NONE -i NONE -n -X -m -n -e -s -S %s -c ':qa!'"
  let args = printf(cmn_args, vim, file)
  call term_sendkeys(buf, args ..
    \ ' ; echo "crash 4: [OK]" >> '.. result .. "\<cr>")
  call TermWait(buf, 150)

      \ 'crash 4: [OK]',
/**/
    2142,