Commit message: patch 9.0.0404: crash when passing invalid arguments to assert_fails()

Problem:    Crash when passing invalid arguments to assert_fails().
Solution:   Check for NULL string.
CVE: CVE-2022-3153
CWE: CWE-476
Vulnerability description: NULL Pointer Dereference in GitHub repository vim/vim prior to 9.0.0404.

Project: vim
File: testing.c

f_test_override(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u *name = (char_u *)"";
    int     val;
    static int save_starting = -1;

    if (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL)
	return;

    name = tv_get_string(&argvars[0]);
    val = (int)tv_get_number(&argvars[1]);

    if (STRCMP(name, (char_u *)"redraw") == 0)
	disable_redraw_for_testing = val;
    else if (STRCMP(name, (char_u *)"redraw_flag") == 0)
	ignore_redraw_flag_for_testing = val;
    else if (STRCMP(name, (char_u *)"char_avail") == 0)
	disable_char_avail_for_testing = val;
    else if (STRCMP(name, (char_u *)"starting") == 0)
    {
	if (val)
	{
	    if (save_starting < 0)
		save_starting = starting;
	    starting = 0;
	}
	else
	{
	    starting = save_starting;
	    save_starting = -1;
	}
    }
    else if (STRCMP(name, (char_u *)"nfa_fail") == 0)
	nfa_fail_for_testing = val;
    else if (STRCMP(name, (char_u *)"no_query_mouse") == 0)
	no_query_mouse_for_testing = val;
    else if (STRCMP(name, (char_u *)"no_wait_return") == 0)
	no_wait_return = val;
    else if (STRCMP(name, (char_u *)"ui_delay") == 0)
	ui_delay_for_testing = val;
    else if (STRCMP(name, (char_u *)"term_props") == 0)
	reset_term_props_on_termresponse = val;
    else if (STRCMP(name, (char_u *)"vterm_title") == 0)
	disable_vterm_title_for_testing = val;
    else if (STRCMP(name, (char_u *)"uptime") == 0)
	override_sysinfo_uptime = val;
    else if (STRCMP(name, (char_u *)"alloc_lines") == 0)
	ml_get_alloc_lines = val;
    else if (STRCMP(name, (char_u *)"autoload") == 0)
	override_autoload = val;
    else if (STRCMP(name, (char_u *)"ALL") == 0)
    {
	disable_char_avail_for_testing = FALSE;
	disable_redraw_for_testing = FALSE;
	ignore_redraw_flag_for_testing = FALSE;
	nfa_fail_for_testing = FALSE;
	no_query_mouse_for_testing = FALSE;
	ui_delay_for_testing = 0;
	reset_term_props_on_termresponse = FALSE;
	override_sysinfo_uptime = -1;
	// ml_get_alloc_lines is not reset by "ALL"
	if (save_starting >= 0)
	{
	    starting = save_starting;
	    save_starting = -1;
	}
    }
    else
	semsg(_(e_invalid_argument_str), name);
}

Project: vim
File: testing.c

f_test_getvalue(typval_T *argvars, typval_T *rettv)
{
    char_u *name;

    if (check_for_string_arg(argvars, 0) == FAIL)
	return;

    name = tv_get_string(&argvars[0]);

    if (STRCMP(name, (char_u *)"need_fileinfo") == 0)
	rettv->vval.v_number = need_fileinfo;
    else
	semsg(_(e_invalid_argument_str), name);
}

Project: vim
File: testing.c

f_assert_fails(typval_T *argvars, typval_T *rettv)
{
    char_u	*cmd;
    garray_T	ga;
    int		save_trylevel = trylevel;
    int		called_emsg_before = called_emsg;
    char	*wrong_arg_msg = NULL;
    char_u	*tofree = NULL;

    if (check_for_string_or_number_arg(argvars, 0) == FAIL
	    || check_for_opt_string_or_list_arg(argvars, 1) == FAIL
	    || (argvars[1].v_type != VAR_UNKNOWN
		&& (argvars[2].v_type != VAR_UNKNOWN
		    && (check_for_opt_number_arg(argvars, 3) == FAIL
			|| (argvars[3].v_type != VAR_UNKNOWN
			    && check_for_opt_string_arg(argvars, 4) == FAIL)))))
	return;

    cmd = tv_get_string_chk(&argvars[0]);

    // trylevel must be zero for a ":throw" command to be considered failed
    trylevel = 0;
    suppress_errthrow = TRUE;
    in_assert_fails = TRUE;

    do_cmdline_cmd(cmd);

    // reset here for any errors reported below
    trylevel = save_trylevel;
    suppress_errthrow = FALSE;

    if (called_emsg == called_emsg_before)
    {
	prepare_assert_error(&ga);
	ga_concat(&ga, (char_u *)"command did not fail: ");
	assert_append_cmd_or_arg(&ga, argvars, cmd);
	assert_error(&ga);
	ga_clear(&ga);
	rettv->vval.v_number = 1;
    }
    else if (argvars[1].v_type != VAR_UNKNOWN)
    {
	char_u	buf[NUMBUFLEN];
	char_u	*expected;
	char_u	*expected_str = NULL;
	int	error_found = FALSE;
	int	error_found_index = 1;
	char_u	*actual = emsg_assert_fails_msg == NULL ? (char_u *)"[unknown]"
						       : emsg_assert_fails_msg;

	if (argvars[1].v_type == VAR_STRING)
	{
	    expected = tv_get_string_buf_chk(&argvars[1], buf);
	    error_found = expected == NULL
			   || strstr((char *)actual, (char *)expected) == NULL;
	}
	else if (argvars[1].v_type == VAR_LIST)
	{
	    list_T	*list = argvars[1].vval.v_list;
	    typval_T	*tv;

	    if (list == NULL || list->lv_len < 1 || list->lv_len > 2)
	    {
		wrong_arg_msg = e_assert_fails_second_arg;
		goto theend;
	    }
	    CHECK_LIST_MATERIALIZE(list);
	    tv = &list->lv_first->li_tv;
	    expected = tv_get_string_buf_chk(tv, buf);
	    if (expected == NULL)
		goto theend;
	    if (!pattern_match(expected, actual, FALSE))
	    {
		error_found = TRUE;
		expected_str = expected;
	    }
	    else if (list->lv_len == 2)
	    {
		// make a copy, an error in pattern_match() may free it
		tofree = actual = vim_strsave(get_vim_var_str(VV_ERRMSG));
		if (actual != NULL)
		{
		    tv = &list->lv_u.mat.lv_last->li_tv;
		    expected = tv_get_string_buf_chk(tv, buf);
		    if (expected == NULL)
			goto theend;
		    if (!pattern_match(expected, actual, FALSE))
		    {
			error_found = TRUE;
			expected_str = expected;
		    }
		}
	    }
	}
	else
	{
	    wrong_arg_msg = e_assert_fails_second_arg;
	    goto theend;
	}

	if (!error_found && argvars[2].v_type != VAR_UNKNOWN
		&& argvars[3].v_type != VAR_UNKNOWN)
	{
	    if (argvars[3].v_type != VAR_NUMBER)
	    {
		wrong_arg_msg = e_assert_fails_fourth_argument;
		goto theend;
	    }
	    else if (argvars[3].vval.v_number >= 0
			 && argvars[3].vval.v_number != emsg_assert_fails_lnum)
	    {
		error_found = TRUE;
		error_found_index = 3;
	    }
	    if (!error_found && argvars[4].v_type != VAR_UNKNOWN)
	    {
		if (argvars[4].v_type != VAR_STRING)
		{
		    wrong_arg_msg = e_assert_fails_fifth_argument;
		    goto theend;
		}
		else if (argvars[4].vval.v_string != NULL
		    && !pattern_match(argvars[4].vval.v_string,
					     emsg_assert_fails_context, FALSE))
		{
		    error_found = TRUE;
		    error_found_index = 4;
		}
	    }
	}

	if (error_found)
	{
	    typval_T actual_tv;

	    prepare_assert_error(&ga);
	    if (error_found_index == 3)
	    {
		actual_tv.v_type = VAR_NUMBER;
		actual_tv.vval.v_number = emsg_assert_fails_lnum;
	    }
	    else if (error_found_index == 4)
	    {
		actual_tv.v_type = VAR_STRING;
		actual_tv.vval.v_string = emsg_assert_fails_context;
	    }
	    else
	    {
		actual_tv.v_type = VAR_STRING;
		actual_tv.vval.v_string = actual;
	    }
	    fill_assert_error(&ga, &argvars[2], expected_str,
			&argvars[error_found_index], &actual_tv, ASSERT_OTHER);
	    ga_concat(&ga, (char_u *)": ");
	    assert_append_cmd_or_arg(&ga, argvars, cmd);
	    assert_error(&ga);
	    ga_clear(&ga);
	    rettv->vval.v_number = 1;
	}
    }

theend:
    trylevel = save_trylevel;
    suppress_errthrow = FALSE;
    in_assert_fails = FALSE;
    did_emsg = FALSE;
    got_int = FALSE;
    msg_col = 0;
    need_wait_return = FALSE;
    emsg_on_display = FALSE;
    msg_scrolled = 0;
    lines_left = Rows;
    VIM_CLEAR(emsg_assert_fails_msg);
    vim_free(tofree);
    set_vim_var_string(VV_ERRMSG, NULL, 0);
    if (wrong_arg_msg != NULL)
	emsg(_(wrong_arg_msg));
}

Project: vim
File: testing.c

f_test_ignore_error(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (check_for_string_arg(argvars, 0) == FAIL)
	return;

    ignore_error_for_testing(tv_get_string(&argvars[0]));
}

Project: vim
File: testing.c

f_test_setmouse(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER)
    {
	emsg(_(e_invalid_argument));
	return;
    }

    mouse_row = (time_t)tv_get_number(&argvars[0]) - 1;
    mouse_col = (time_t)tv_get_number(&argvars[1]) - 1;
}

Project: vim
File: testing.c

f_test_option_not_set(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u *name = (char_u *)"";

    if (check_for_string_arg(argvars, 0) == FAIL)
	return;

    name = tv_get_string(&argvars[0]);
    if (reset_option_was_set(name) == FAIL)
	semsg(_(e_invalid_argument_str), name);
}