Fix oobread bug in NE parser ##crash

* Reported by @cnitlrt via huntrdev
* BountyID: 02b4b563-b946-4343-9092-38d1c5cd60c9
* Reproducer: neoobread

RCoreSymCacheElement *r_coresym_cache_element_new(RBinFile *bf, RBuffer *buf, ut64 off, int bits, char * file_name) {
	RCoreSymCacheElement *result = NULL;
	ut8 *b = NULL;
	RCoreSymCacheElementHdr *hdr = r_coresym_cache_element_header_new (buf, off, bits);
	if (!hdr) {
		return NULL;
	}
	if (hdr->version != 1) {
		eprintf ("Unsupported CoreSymbolication cache version (%d)\n", hdr->version);
		goto beach;
	}
	if (hdr->size == 0 || hdr->size > r_buf_size (buf) - off) {
		eprintf ("Corrupted CoreSymbolication header: size out of bounds (0x%x)\n", hdr->size);
		goto beach;
	}
	result = R_NEW0 (RCoreSymCacheElement);
	if (!result) {
		goto beach;
	}
	result->hdr = hdr;
	b = malloc (hdr->size);
	if (!b) {
		goto beach;
	}
	if (r_buf_read_at (buf, off, b, hdr->size) != hdr->size) {
		goto beach;
	}
	ut8 *end = b + hdr->size;
	if (file_name) {
		result->file_name = file_name;
	} else if (hdr->file_name_off) {
		result->file_name = str_dup_safe (b, b + (size_t)hdr->file_name_off, end);
	}
	if (hdr->version_off) {
		result->binary_version = str_dup_safe (b, b + (size_t)hdr->version_off, end);
	}
	const size_t word_size = bits / 8;
	const ut64 start_of_sections = (ut64)hdr->n_segments * R_CS_EL_SIZE_SEG + R_CS_EL_OFF_SEGS;
	const ut64 sect_size = (bits == 32) ? R_CS_EL_SIZE_SECT_32 : R_CS_EL_SIZE_SECT_64;
	const ut64 start_of_symbols = start_of_sections + (ut64)hdr->n_sections * sect_size;
	const ut64 start_of_lined_symbols = start_of_symbols + (ut64)hdr->n_symbols * R_CS_EL_SIZE_SYM;
	const ut64 start_of_line_info = start_of_lined_symbols + (ut64)hdr->n_lined_symbols * R_CS_EL_SIZE_LSYM;
	const ut64 start_of_unknown_pairs = start_of_line_info + (ut64)hdr->n_line_info * R_CS_EL_SIZE_LINFO;
	const ut64 start_of_strings = start_of_unknown_pairs + (ut64)hdr->n_symbols * 8;

	ut64 page_zero_size = 0;
	size_t page_zero_idx = 0;
	if (UT32_MUL_OVFCHK (hdr->n_segments, sizeof (RCoreSymCacheElementSegment))) {
		goto beach;
	} else if (UT32_MUL_OVFCHK (hdr->n_sections, sizeof (RCoreSymCacheElementSection))) {
		goto beach;
	} else if (UT32_MUL_OVFCHK (hdr->n_symbols, sizeof (RCoreSymCacheElementSymbol))) {
		goto beach;
	} else if (UT32_MUL_OVFCHK (hdr->n_lined_symbols, sizeof (RCoreSymCacheElementLinedSymbol))) {
		goto beach;
	} else if (UT32_MUL_OVFCHK (hdr->n_line_info, sizeof (RCoreSymCacheElementLineInfo))) {
		goto beach;
	}
	if (hdr->n_segments > 0) {
		result->segments = R_NEWS0 (RCoreSymCacheElementSegment, hdr->n_segments);
		if (!result->segments) {
			goto beach;
		}
		size_t i;
		ut8 *cursor = b + R_CS_EL_OFF_SEGS;
		for (i = 0; i < hdr->n_segments && cursor + sizeof (RCoreSymCacheElementSegment) < end; i++) {
			RCoreSymCacheElementSegment *seg = &result->segments[i];
			seg->paddr = seg->vaddr = r_read_le64 (cursor);
			cursor += 8;
			if (cursor >= end) {
				break;
			}
			seg->size = seg->vsize = r_read_le64 (cursor);
			cursor += 8;
			if (cursor >= end) {
				break;
			}
			seg->name = str_dup_safe_fixed (b, cursor, 16, end);
			cursor += 16;
			if (!seg->name) {
				continue;
			}

			if (!strcmp (seg->name, "__PAGEZERO")) {
				page_zero_size = seg->size;
				page_zero_idx = i;
				seg->paddr = seg->vaddr = 0;
				seg->size = 0;
			}
		}
		for (i = 0; i < hdr->n_segments && page_zero_size > 0; i++) {
			if (i == page_zero_idx) {
				continue;
			}
			RCoreSymCacheElementSegment *seg = &result->segments[i];
			if (seg->vaddr < page_zero_size) {
				seg->vaddr += page_zero_size;
			}
		}
	}
	bool relative_to_strings = false;
	ut8* string_origin;
	if (hdr->n_sections > 0) {
		result->sections = R_NEWS0 (RCoreSymCacheElementSection, hdr->n_sections);
		if (!result->sections) {
			goto beach;
		}
		size_t i;
		ut8 *cursor = b + start_of_sections;
		for (i = 0; i < hdr->n_sections && cursor < end; i++) {
			ut8 *sect_start = cursor;
			RCoreSymCacheElementSection *sect = &result->sections[i];
			sect->vaddr = sect->paddr = r_read_ble (cursor, false, bits);
			if (sect->vaddr < page_zero_size) {
				sect->vaddr += page_zero_size;
			}
			cursor += word_size;
			if (cursor >= end) {
				break;
			}
			sect->size = r_read_ble (cursor, false, bits);
			cursor += word_size;
			if (cursor >= end) {
				break;
			}
			ut64 sect_name_off = r_read_ble (cursor, false, bits);
			if (!i && !sect_name_off) {
				relative_to_strings = true;
			}
			cursor += word_size;
			if (bits == 32) {
				cursor += word_size;
			}
			string_origin = relative_to_strings? b + start_of_strings : sect_start;
			sect->name = str_dup_safe (b, string_origin + (size_t)sect_name_off, end);
		}
	}
	if (hdr->n_symbols) {
		result->symbols = R_NEWS0 (RCoreSymCacheElementSymbol, hdr->n_symbols);
		if (!result->symbols) {
			goto beach;
		}
		size_t i;
		ut8 *cursor = b + start_of_symbols;
		for (i = 0; i < hdr->n_symbols && cursor + R_CS_EL_SIZE_SYM <= end; i++) {
			RCoreSymCacheElementSymbol *sym = &result->symbols[i];
			sym->paddr = r_read_le32 (cursor);
			sym->size = r_read_le32 (cursor + 0x4);
			sym->unk1 = r_read_le32 (cursor + 0x8);
			size_t name_off = r_read_le32 (cursor + 0xc);
			size_t mangled_name_off = r_read_le32 (cursor + 0x10);
			sym->unk2 = (st32)r_read_le32 (cursor + 0x14);
			string_origin = relative_to_strings? b + start_of_strings : cursor;
			sym->name = str_dup_safe (b, string_origin + name_off, end);
			if (!sym->name) {
				cursor += R_CS_EL_SIZE_SYM;
				continue;
			}
			string_origin = relative_to_strings? b + start_of_strings : cursor;
			sym->mangled_name = str_dup_safe (b, string_origin + mangled_name_off, end);
			if (!sym->mangled_name) {
				cursor += R_CS_EL_SIZE_SYM;
				continue;
			}
			cursor += R_CS_EL_SIZE_SYM;
		}
	}
	if (hdr->n_lined_symbols) {
		result->lined_symbols = R_NEWS0 (RCoreSymCacheElementLinedSymbol, hdr->n_lined_symbols);
		if (!result->lined_symbols) {
			goto beach;
		}
		size_t i;
		ut8 *cursor = b + start_of_lined_symbols;
		for (i = 0; i < hdr->n_lined_symbols && cursor + R_CS_EL_SIZE_LSYM <= end; i++) {
			RCoreSymCacheElementLinedSymbol *lsym = &result->lined_symbols[i];
			lsym->sym.paddr = r_read_le32 (cursor);
			lsym->sym.size = r_read_le32 (cursor + 0x4);
			lsym->sym.unk1 = r_read_le32 (cursor + 0x8);
			size_t name_off = r_read_le32 (cursor + 0xc);
			size_t mangled_name_off = r_read_le32 (cursor + 0x10);
			lsym->sym.unk2 = (st32)r_read_le32 (cursor + 0x14);
			size_t file_name_off = r_read_le32 (cursor + 0x18);
			lsym->flc.line = r_read_le32 (cursor + 0x1c);
			lsym->flc.col = r_read_le32 (cursor + 0x20);
			string_origin = relative_to_strings? b + start_of_strings : cursor;
			lsym->sym.name = str_dup_safe (b, string_origin + name_off, end);
			if (!lsym->sym.name) {
				cursor += R_CS_EL_SIZE_LSYM;
				continue;
			}
			string_origin = relative_to_strings? b + start_of_strings : cursor;
			lsym->sym.mangled_name = str_dup_safe (b, string_origin + mangled_name_off, end);
			if (!lsym->sym.mangled_name) {
				cursor += R_CS_EL_SIZE_LSYM;
				continue;
			}
			string_origin = relative_to_strings? b + start_of_strings : cursor;
			lsym->flc.file = str_dup_safe (b, string_origin + file_name_off, end);
			if (!lsym->flc.file) {
				cursor += R_CS_EL_SIZE_LSYM;
				continue;
			}
			cursor += R_CS_EL_SIZE_LSYM;
			meta_add_fileline (bf, r_coresym_cache_element_pa2va (result, lsym->sym.paddr), lsym->sym.size, &lsym->flc);
		}
	}
	if (hdr->n_line_info) {
		result->line_info = R_NEWS0 (RCoreSymCacheElementLineInfo, hdr->n_line_info);
		if (!result->line_info) {
			goto beach;
		}
		size_t i;
		ut8 *cursor = b + start_of_line_info;
		for (i = 0; i < hdr->n_line_info && cursor + R_CS_EL_SIZE_LINFO <= end; i++) {
			RCoreSymCacheElementLineInfo *info = &result->line_info[i];
			info->paddr = r_read_le32 (cursor);
			info->size = r_read_le32 (cursor + 4);
			size_t file_name_off = r_read_le32 (cursor + 8);
			info->flc.line = r_read_le32 (cursor + 0xc);
			info->flc.col = r_read_le32 (cursor + 0x10);
			string_origin = relative_to_strings? b + start_of_strings : cursor;
			info->flc.file = str_dup_safe (b, string_origin + file_name_off, end);
			if (!info->flc.file) {
				break;
			}
			cursor += R_CS_EL_SIZE_LINFO;
			meta_add_fileline (bf, r_coresym_cache_element_pa2va (result, info->paddr), info->size, &info->flc);
		}
	}

	/*
	 * TODO:
	 * Figure out the meaning of the 2 arrays of hdr->n_symbols
	 * 32-bit integers located at the end of line info.
	 * Those are the last info before the strings at the end.
	 */

beach:
	free (b);
	return result;
}

Heap-based Buffer Overflow in GitHub repository radareorg/radare2 prior to 5.6.8. The bug causes the program reads data past the end of the intented buffer. Typically, this can allow attackers to read sensitive information from other memory locations or cause a crash.


patch 8.2.4763: using invalid pointer with "V:" in Ex mode

Problem:    Using invalid pointer with "V:" in Ex mode.
Solution:   Correctly handle the command being changed to "+".

parse_command_modifiers(
	exarg_T	    *eap,
	char	    **errormsg,
	cmdmod_T    *cmod,
	int	    skip_only)
{
    char_u  *cmd_start = NULL;
    char_u  *p;
    int	    starts_with_colon = FALSE;
    int	    vim9script = in_vim9script();
    int	    has_visual_range = FALSE;

    CLEAR_POINTER(cmod);
    cmod->cmod_flags = sticky_cmdmod_flags;

    if (STRNCMP(eap->cmd, "'<,'>", 5) == 0)
    {
	// The automatically inserted Visual area range is skipped, so that
	// typing ":cmdmod cmd" in Visual mode works without having to move the
	// range to after the modififiers.
	eap->cmd += 5;
	cmd_start = eap->cmd;
	has_visual_range = TRUE;
    }

    // Repeat until no more command modifiers are found.
    for (;;)
    {
	while (*eap->cmd == ' ' || *eap->cmd == '\t' || *eap->cmd == ':')
	{
	    if (*eap->cmd == ':')
		starts_with_colon = TRUE;
	    ++eap->cmd;
	}

	// in ex mode, an empty line works like :+
	if (*eap->cmd == NUL && exmode_active
		   && (getline_equal(eap->getline, eap->cookie, getexmodeline)
		       || getline_equal(eap->getline, eap->cookie, getexline))
			&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count)
	{
	    eap->cmd = (char_u *)"+";
	    if (!skip_only)
		ex_pressedreturn = TRUE;
	}

	// ignore comment and empty lines
	if (comment_start(eap->cmd, starts_with_colon))
	{
	    // a comment ends at a NL
	    if (eap->nextcmd == NULL)
	    {
		eap->nextcmd = vim_strchr(eap->cmd, '\n');
		if (eap->nextcmd != NULL)
		    ++eap->nextcmd;
	    }
	    if (vim9script && has_cmdmod(cmod, FALSE))
		*errormsg = _(e_command_modifier_without_command);
	    return FAIL;
	}
	if (*eap->cmd == NUL)
	{
	    if (!skip_only)
	    {
		ex_pressedreturn = TRUE;
		if (vim9script && has_cmdmod(cmod, FALSE))
		    *errormsg = _(e_command_modifier_without_command);
	    }
	    return FAIL;
	}

	p = skip_range(eap->cmd, TRUE, NULL);

	// In Vim9 script a variable can shadow a command modifier:
	//   verbose = 123
	//   verbose += 123
	//   silent! verbose = func()
	//   verbose.member = 2
	//   verbose[expr] = 2
	// But not:
	//   verbose [a, b] = list
	if (vim9script)
	{
	    char_u *s, *n;

	    for (s = eap->cmd; ASCII_ISALPHA(*s); ++s)
		;
	    n = skipwhite(s);
	    if (*n == '.' || *n == '=' || (*n != NUL && n[1] == '=')
		    || *s == '[')
		break;
	}

	switch (*p)
	{
	    // When adding an entry, also modify cmd_exists().
	    case 'a':	if (!checkforcmd_noparen(&eap->cmd, "aboveleft", 3))
			    break;
			cmod->cmod_split |= WSP_ABOVE;
			continue;

	    case 'b':	if (checkforcmd_noparen(&eap->cmd, "belowright", 3))
			{
			    cmod->cmod_split |= WSP_BELOW;
			    continue;
			}
			if (checkforcmd_opt(&eap->cmd, "browse", 3, TRUE))
			{
#ifdef FEAT_BROWSE_CMD
			    cmod->cmod_flags |= CMOD_BROWSE;
#endif
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "botright", 2))
			    break;
			cmod->cmod_split |= WSP_BOT;
			continue;

	    case 'c':	if (!checkforcmd_opt(&eap->cmd, "confirm", 4, TRUE))
			    break;
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
			cmod->cmod_flags |= CMOD_CONFIRM;
#endif
			continue;

	    case 'k':	if (checkforcmd_noparen(&eap->cmd, "keepmarks", 3))
			{
			    cmod->cmod_flags |= CMOD_KEEPMARKS;
			    continue;
			}
			if (checkforcmd_noparen(&eap->cmd, "keepalt", 5))
			{
			    cmod->cmod_flags |= CMOD_KEEPALT;
			    continue;
			}
			if (checkforcmd_noparen(&eap->cmd, "keeppatterns", 5))
			{
			    cmod->cmod_flags |= CMOD_KEEPPATTERNS;
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "keepjumps", 5))
			    break;
			cmod->cmod_flags |= CMOD_KEEPJUMPS;
			continue;

	    case 'f':	// only accept ":filter {pat} cmd"
			{
			    char_u  *reg_pat;
			    char_u  *nulp = NULL;
			    int	    c = 0;

			    if (!checkforcmd_noparen(&p, "filter", 4)
				    || *p == NUL
				    || (ends_excmd(*p)
#ifdef FEAT_EVAL
					// in ":filter #pat# cmd" # does not
					// start a comment
				     && (!vim9script || VIM_ISWHITE(p[1]))
#endif
				     ))
				break;
			    if (*p == '!')
			    {
				cmod->cmod_filter_force = TRUE;
				p = skipwhite(p + 1);
				if (*p == NUL || ends_excmd(*p))
				    break;
			    }
#ifdef FEAT_EVAL
			    // Avoid that "filter(arg)" is recognized.
			    if (vim9script && !VIM_ISWHITE(p[-1]))
				break;
#endif
			    if (skip_only)
				p = skip_vimgrep_pat(p, NULL, NULL);
			    else
				// NOTE: This puts a NUL after the pattern.
				p = skip_vimgrep_pat_ext(p, &reg_pat, NULL,
								    &nulp, &c);
			    if (p == NULL || *p == NUL)
				break;
			    if (!skip_only)
			    {
				cmod->cmod_filter_regmatch.regprog =
						vim_regcomp(reg_pat, RE_MAGIC);
				if (cmod->cmod_filter_regmatch.regprog == NULL)
				    break;
				// restore the character overwritten by NUL
				if (nulp != NULL)
				    *nulp = c;
			    }
			    eap->cmd = p;
			    continue;
			}

			// ":hide" and ":hide | cmd" are not modifiers
	    case 'h':	if (p != eap->cmd || !checkforcmd_noparen(&p, "hide", 3)
					       || *p == NUL || ends_excmd(*p))
			    break;
			eap->cmd = p;
			cmod->cmod_flags |= CMOD_HIDE;
			continue;

	    case 'l':	if (checkforcmd_noparen(&eap->cmd, "lockmarks", 3))
			{
			    cmod->cmod_flags |= CMOD_LOCKMARKS;
			    continue;
			}
			if (checkforcmd_noparen(&eap->cmd, "legacy", 3))
			{
			    if (ends_excmd2(p, eap->cmd))
			    {
				*errormsg =
				      _(e_legacy_must_be_followed_by_command);
				return FAIL;
			    }
			    cmod->cmod_flags |= CMOD_LEGACY;
			    continue;
			}

			if (!checkforcmd_noparen(&eap->cmd, "leftabove", 5))
			    break;
			cmod->cmod_split |= WSP_ABOVE;
			continue;

	    case 'n':	if (checkforcmd_noparen(&eap->cmd, "noautocmd", 3))
			{
			    cmod->cmod_flags |= CMOD_NOAUTOCMD;
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "noswapfile", 3))
			    break;
			cmod->cmod_flags |= CMOD_NOSWAPFILE;
			continue;

	    case 'r':	if (!checkforcmd_noparen(&eap->cmd, "rightbelow", 6))
			    break;
			cmod->cmod_split |= WSP_BELOW;
			continue;

	    case 's':	if (checkforcmd_noparen(&eap->cmd, "sandbox", 3))
			{
			    cmod->cmod_flags |= CMOD_SANDBOX;
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "silent", 3))
			    break;
			cmod->cmod_flags |= CMOD_SILENT;
			if (*eap->cmd == '!' && !VIM_ISWHITE(eap->cmd[-1]))
			{
			    // ":silent!", but not "silent !cmd"
			    eap->cmd = skipwhite(eap->cmd + 1);
			    cmod->cmod_flags |= CMOD_ERRSILENT;
			}
			continue;

	    case 't':	if (checkforcmd_noparen(&p, "tab", 3))
			{
			    if (!skip_only)
			    {
				long tabnr = get_address(eap, &eap->cmd,
						    ADDR_TABS, eap->skip,
						    skip_only, FALSE, 1);
				if (tabnr == MAXLNUM)
				    cmod->cmod_tab = tabpage_index(curtab) + 1;
				else
				{
				    if (tabnr < 0 || tabnr > LAST_TAB_NR)
				    {
					*errormsg = _(e_invalid_range);
					return FAIL;
				    }
				    cmod->cmod_tab = tabnr + 1;
				}
			    }
			    eap->cmd = p;
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "topleft", 2))
			    break;
			cmod->cmod_split |= WSP_TOP;
			continue;

	    case 'u':	if (!checkforcmd_noparen(&eap->cmd, "unsilent", 3))
			    break;
			cmod->cmod_flags |= CMOD_UNSILENT;
			continue;

	    case 'v':	if (checkforcmd_noparen(&eap->cmd, "vertical", 4))
			{
			    cmod->cmod_split |= WSP_VERT;
			    continue;
			}
			if (checkforcmd_noparen(&eap->cmd, "vim9cmd", 4))
			{
			    if (ends_excmd2(p, eap->cmd))
			    {
				*errormsg =
				      _(e_vim9cmd_must_be_followed_by_command);
				return FAIL;
			    }
			    cmod->cmod_flags |= CMOD_VIM9CMD;
			    continue;
			}
			if (!checkforcmd_noparen(&p, "verbose", 4))
			    break;
			if (vim_isdigit(*eap->cmd))
			{
			    cmod->cmod_verbose = atoi((char *)eap->cmd);
			    if (cmod->cmod_verbose == 0)
				cmod->cmod_verbose = -1;
			}
			else
			    cmod->cmod_verbose = 1;
			eap->cmd = p;
			continue;
	}
	break;
    }

    if (has_visual_range)
    {
	if (eap->cmd > cmd_start)
	{
	    // Move the '<,'> range to after the modifiers and insert a colon.
	    // Since the modifiers have been parsed put the colon on top of the
	    // space: "'<,'>mod cmd" -> "mod:'<,'>cmd
	    // Put eap->cmd after the colon.
	    mch_memmove(cmd_start - 5, cmd_start, eap->cmd - cmd_start);
	    eap->cmd -= 5;
	    mch_memmove(eap->cmd - 1, ":'<,'>", 6);
	}
	else
	    // no modifiers, move the pointer back
	    eap->cmd -= 5;
    }

    return OK;
}

global heap buffer overflow in skip_range in GitHub repository vim/vim prior to 8.2.4763. This vulnerability is capable of crashing software, Bypass Protection Mechanism, Modify Memory, and possible remote execution


patch 9.0.0025: accessing beyond allocated memory with the cmdline window

Problem:    Accessing beyond allocated memory when using the cmdline window in
            Ex mode.
Solution:   Use "*" instead of "'<,'>" for Visual mode.

parse_command_modifiers(
	exarg_T	    *eap,
	char	    **errormsg,
	cmdmod_T    *cmod,
	int	    skip_only)
{
    char_u  *orig_cmd = eap->cmd;
    char_u  *cmd_start = NULL;
    int	    use_plus_cmd = FALSE;
    int	    starts_with_colon = FALSE;
    int	    vim9script = in_vim9script();
    int	    has_visual_range = FALSE;

    CLEAR_POINTER(cmod);
    cmod->cmod_flags = sticky_cmdmod_flags;

    if (STRNCMP(eap->cmd, "'<,'>", 5) == 0)
    {
	// The automatically inserted Visual area range is skipped, so that
	// typing ":cmdmod cmd" in Visual mode works without having to move the
	// range to after the modififiers. The command will be
	// "'<,'>cmdmod cmd", parse "cmdmod cmd" and then put back "'<,'>"
	// before "cmd" below.
	eap->cmd += 5;
	cmd_start = eap->cmd;
	has_visual_range = TRUE;
    }

    // Repeat until no more command modifiers are found.
    for (;;)
    {
	char_u  *p;

	while (*eap->cmd == ' ' || *eap->cmd == '\t' || *eap->cmd == ':')
	{
	    if (*eap->cmd == ':')
		starts_with_colon = TRUE;
	    ++eap->cmd;
	}

	// in ex mode, an empty command (after modifiers) works like :+
	if (*eap->cmd == NUL && exmode_active
		   && (getline_equal(eap->getline, eap->cookie, getexmodeline)
		       || getline_equal(eap->getline, eap->cookie, getexline))
			&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count)
	{
	    use_plus_cmd = TRUE;
	    if (!skip_only)
		ex_pressedreturn = TRUE;
	    break;  // no modifiers following
	}

	// ignore comment and empty lines
	if (comment_start(eap->cmd, starts_with_colon))
	{
	    // a comment ends at a NL
	    if (eap->nextcmd == NULL)
	    {
		eap->nextcmd = vim_strchr(eap->cmd, '\n');
		if (eap->nextcmd != NULL)
		    ++eap->nextcmd;
	    }
	    if (vim9script && has_cmdmod(cmod, FALSE))
		*errormsg = _(e_command_modifier_without_command);
	    return FAIL;
	}
	if (*eap->cmd == NUL)
	{
	    if (!skip_only)
	    {
		ex_pressedreturn = TRUE;
		if (vim9script && has_cmdmod(cmod, FALSE))
		    *errormsg = _(e_command_modifier_without_command);
	    }
	    return FAIL;
	}

	p = skip_range(eap->cmd, TRUE, NULL);

	// In Vim9 script a variable can shadow a command modifier:
	//   verbose = 123
	//   verbose += 123
	//   silent! verbose = func()
	//   verbose.member = 2
	//   verbose[expr] = 2
	// But not:
	//   verbose [a, b] = list
	if (vim9script)
	{
	    char_u *s, *n;

	    for (s = eap->cmd; ASCII_ISALPHA(*s); ++s)
		;
	    n = skipwhite(s);
	    if (*n == '.' || *n == '=' || (*n != NUL && n[1] == '=')
		    || *s == '[')
		break;
	}

	switch (*p)
	{
	    // When adding an entry, also modify cmd_exists().
	    case 'a':	if (!checkforcmd_noparen(&eap->cmd, "aboveleft", 3))
			    break;
			cmod->cmod_split |= WSP_ABOVE;
			continue;

	    case 'b':	if (checkforcmd_noparen(&eap->cmd, "belowright", 3))
			{
			    cmod->cmod_split |= WSP_BELOW;
			    continue;
			}
			if (checkforcmd_opt(&eap->cmd, "browse", 3, TRUE))
			{
#ifdef FEAT_BROWSE_CMD
			    cmod->cmod_flags |= CMOD_BROWSE;
#endif
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "botright", 2))
			    break;
			cmod->cmod_split |= WSP_BOT;
			continue;

	    case 'c':	if (!checkforcmd_opt(&eap->cmd, "confirm", 4, TRUE))
			    break;
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
			cmod->cmod_flags |= CMOD_CONFIRM;
#endif
			continue;

	    case 'k':	if (checkforcmd_noparen(&eap->cmd, "keepmarks", 3))
			{
			    cmod->cmod_flags |= CMOD_KEEPMARKS;
			    continue;
			}
			if (checkforcmd_noparen(&eap->cmd, "keepalt", 5))
			{
			    cmod->cmod_flags |= CMOD_KEEPALT;
			    continue;
			}
			if (checkforcmd_noparen(&eap->cmd, "keeppatterns", 5))
			{
			    cmod->cmod_flags |= CMOD_KEEPPATTERNS;
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "keepjumps", 5))
			    break;
			cmod->cmod_flags |= CMOD_KEEPJUMPS;
			continue;

	    case 'f':	// only accept ":filter {pat} cmd"
			{
			    char_u  *reg_pat;
			    char_u  *nulp = NULL;
			    int	    c = 0;

			    if (!checkforcmd_noparen(&p, "filter", 4)
				    || *p == NUL
				    || (ends_excmd(*p)
#ifdef FEAT_EVAL
					// in ":filter #pat# cmd" # does not
					// start a comment
				     && (!vim9script || VIM_ISWHITE(p[1]))
#endif
				     ))
				break;
			    if (*p == '!')
			    {
				cmod->cmod_filter_force = TRUE;
				p = skipwhite(p + 1);
				if (*p == NUL || ends_excmd(*p))
				    break;
			    }
#ifdef FEAT_EVAL
			    // Avoid that "filter(arg)" is recognized.
			    if (vim9script && !VIM_ISWHITE(p[-1]))
				break;
#endif
			    if (skip_only)
				p = skip_vimgrep_pat(p, NULL, NULL);
			    else
				// NOTE: This puts a NUL after the pattern.
				p = skip_vimgrep_pat_ext(p, &reg_pat, NULL,
								    &nulp, &c);
			    if (p == NULL || *p == NUL)
				break;
			    if (!skip_only)
			    {
				cmod->cmod_filter_regmatch.regprog =
						vim_regcomp(reg_pat, RE_MAGIC);
				if (cmod->cmod_filter_regmatch.regprog == NULL)
				    break;
				// restore the character overwritten by NUL
				if (nulp != NULL)
				    *nulp = c;
			    }
			    eap->cmd = p;
			    continue;
			}

			// ":hide" and ":hide | cmd" are not modifiers
	    case 'h':	if (p != eap->cmd || !checkforcmd_noparen(&p, "hide", 3)
					       || *p == NUL || ends_excmd(*p))
			    break;
			eap->cmd = p;
			cmod->cmod_flags |= CMOD_HIDE;
			continue;

	    case 'l':	if (checkforcmd_noparen(&eap->cmd, "lockmarks", 3))
			{
			    cmod->cmod_flags |= CMOD_LOCKMARKS;
			    continue;
			}
			if (checkforcmd_noparen(&eap->cmd, "legacy", 3))
			{
			    if (ends_excmd2(p, eap->cmd))
			    {
				*errormsg =
				      _(e_legacy_must_be_followed_by_command);
				return FAIL;
			    }
			    cmod->cmod_flags |= CMOD_LEGACY;
			    continue;
			}

			if (!checkforcmd_noparen(&eap->cmd, "leftabove", 5))
			    break;
			cmod->cmod_split |= WSP_ABOVE;
			continue;

	    case 'n':	if (checkforcmd_noparen(&eap->cmd, "noautocmd", 3))
			{
			    cmod->cmod_flags |= CMOD_NOAUTOCMD;
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "noswapfile", 3))
			    break;
			cmod->cmod_flags |= CMOD_NOSWAPFILE;
			continue;

	    case 'r':	if (!checkforcmd_noparen(&eap->cmd, "rightbelow", 6))
			    break;
			cmod->cmod_split |= WSP_BELOW;
			continue;

	    case 's':	if (checkforcmd_noparen(&eap->cmd, "sandbox", 3))
			{
			    cmod->cmod_flags |= CMOD_SANDBOX;
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "silent", 3))
			    break;
			cmod->cmod_flags |= CMOD_SILENT;
			if (*eap->cmd == '!' && !VIM_ISWHITE(eap->cmd[-1]))
			{
			    // ":silent!", but not "silent !cmd"
			    eap->cmd = skipwhite(eap->cmd + 1);
			    cmod->cmod_flags |= CMOD_ERRSILENT;
			}
			continue;

	    case 't':	if (checkforcmd_noparen(&p, "tab", 3))
			{
			    if (!skip_only)
			    {
				long tabnr = get_address(eap, &eap->cmd,
						    ADDR_TABS, eap->skip,
						    skip_only, FALSE, 1);
				if (tabnr == MAXLNUM)
				    cmod->cmod_tab = tabpage_index(curtab) + 1;
				else
				{
				    if (tabnr < 0 || tabnr > LAST_TAB_NR)
				    {
					*errormsg = _(e_invalid_range);
					return FAIL;
				    }
				    cmod->cmod_tab = tabnr + 1;
				}
			    }
			    eap->cmd = p;
			    continue;
			}
			if (!checkforcmd_noparen(&eap->cmd, "topleft", 2))
			    break;
			cmod->cmod_split |= WSP_TOP;
			continue;

	    case 'u':	if (!checkforcmd_noparen(&eap->cmd, "unsilent", 3))
			    break;
			cmod->cmod_flags |= CMOD_UNSILENT;
			continue;

	    case 'v':	if (checkforcmd_noparen(&eap->cmd, "vertical", 4))
			{
			    cmod->cmod_split |= WSP_VERT;
			    continue;
			}
			if (checkforcmd_noparen(&eap->cmd, "vim9cmd", 4))
			{
			    if (ends_excmd2(p, eap->cmd))
			    {
				*errormsg =
				      _(e_vim9cmd_must_be_followed_by_command);
				return FAIL;
			    }
			    cmod->cmod_flags |= CMOD_VIM9CMD;
			    continue;
			}
			if (!checkforcmd_noparen(&p, "verbose", 4))
			    break;
			if (vim_isdigit(*eap->cmd))
			{
			    // zero means not set, one is verbose == 0, etc.
			    cmod->cmod_verbose = atoi((char *)eap->cmd) + 1;
			}
			else
			    cmod->cmod_verbose = 2;  // default: verbose == 1
			eap->cmd = p;
			continue;
	}
	break;
    }

    if (has_visual_range)
    {
	if (eap->cmd > cmd_start)
	{
	    // Move the '<,'> range to after the modifiers and insert a colon.
	    // Since the modifiers have been parsed put the colon on top of the
	    // space: "'<,'>mod cmd" -> "mod:'<,'>cmd
	    // Put eap->cmd after the colon.
	    if (use_plus_cmd)
	    {
		size_t len = STRLEN(cmd_start);

		// Special case: empty command uses "+":
		//  "'<,'>mods" -> "mods'<,'>+
		mch_memmove(orig_cmd, cmd_start, len);
		STRCPY(orig_cmd + len, "'<,'>+");
	    }
	    else
	    {
		mch_memmove(cmd_start - 5, cmd_start, eap->cmd - cmd_start);
		eap->cmd -= 5;
		mch_memmove(eap->cmd - 1, ":'<,'>", 6);
	    }
	}
	else
	    // No modifiers, move the pointer back.
	    // Special case: change empty command to "+".
	    if (use_plus_cmd)
		eap->cmd = (char_u *)"'<,'>+";
	    else
		eap->cmd = orig_cmd;
    }
    else if (use_plus_cmd)
	eap->cmd = (char_u *)"+";

    return OK;
}

Out-of-bounds Write in GitHub repository vim/vim prior to 9.0.


Fix UAF in aaaa on arm/thumb switching ##crash

* Reported by @peacock-doris via huntr.dev
* Reproducer tests_65185
* This is a logic fix, but not the fully safe as changes in the code
  can result on UAF again, to properly protect r2 from crashing we
  need to break the ABI and add refcounting to RRegItem, which can't
  happen in 5.6.x because of abi-compat rules

static void extract_arg(RAnal *anal, RAnalFunction *fcn, RAnalOp *op, const char *reg, const char *sign, char type) {
	st64 ptr = 0;
	char *addr, *esil_buf = NULL;
	const st64 maxstackframe = 1024 * 8;

	r_return_if_fail (anal && fcn && op && reg);

	size_t i;
	for (i = 0; i < R_ARRAY_SIZE (op->src); i++) {
		if (op->src[i] && op->src[i]->reg && op->src[i]->reg->name) {
			if (!strcmp (reg, op->src[i]->reg->name)) {
				st64 delta = op->src[i]->delta;
				if ((delta > 0 && *sign == '+') || (delta < 0 && *sign == '-')) {
					ptr = R_ABS (op->src[i]->delta);
					break;
				}
			}
		}
	}

	if (!ptr) {
		const char *op_esil = r_strbuf_get (&op->esil);
		if (!op_esil) {
			return;
		}
		esil_buf = strdup (op_esil);
		if (!esil_buf) {
			return;
		}
		r_strf_var (esilexpr, 64, ",%s,%s,", reg, sign);
		char *ptr_end = strstr (esil_buf, esilexpr);
		if (!ptr_end) {
			free (esil_buf);
			return;
		}
		*ptr_end = 0;
		addr = ptr_end;
		while ((addr[0] != '0' || addr[1] != 'x') && addr >= esil_buf + 1 && *addr != ',') {
			addr--;
		}
		if (strncmp (addr, "0x", 2)) {
			//XXX: This is a workaround for inconsistent esil
			if (!op->stackop && op->dst) {
				const char *sp = r_reg_get_name (anal->reg, R_REG_NAME_SP);
				const char *bp = r_reg_get_name (anal->reg, R_REG_NAME_BP);
				const char *rn = op->dst->reg ? op->dst->reg->name : NULL;
				if (rn && ((bp && !strcmp (bp, rn)) || (sp && !strcmp (sp, rn)))) {
					if (anal->verbose) {
						eprintf ("Warning: Analysis didn't fill op->stackop for instruction that alters stack at 0x%" PFMT64x ".\n", op->addr);
					}
					goto beach;
				}
			}
			if (*addr == ',') {
				addr++;
			}
			if (!op->stackop && op->type != R_ANAL_OP_TYPE_PUSH && op->type != R_ANAL_OP_TYPE_POP
				&& op->type != R_ANAL_OP_TYPE_RET && r_str_isnumber (addr)) {
				ptr = (st64)r_num_get (NULL, addr);
				if (ptr && op->src[0] && ptr == op->src[0]->imm) {
					goto beach;
				}
			} else if ((op->stackop == R_ANAL_STACK_SET) || (op->stackop == R_ANAL_STACK_GET)) {
				if (op->ptr % 4) {
					goto beach;
				}
				ptr = R_ABS (op->ptr);
			} else {
				goto beach;
			}
		} else {
			ptr = (st64)r_num_get (NULL, addr);
		}
	}

	if (anal->verbose && (!op->src[0] || !op->dst)) {
		eprintf ("Warning: Analysis didn't fill op->src/dst at 0x%" PFMT64x ".\n", op->addr);
	}

	int rw = (op->direction == R_ANAL_OP_DIR_WRITE) ? R_ANAL_VAR_ACCESS_TYPE_WRITE : R_ANAL_VAR_ACCESS_TYPE_READ;
	if (*sign == '+') {
		const bool isarg = type == R_ANAL_VAR_KIND_SPV ? ptr >= fcn->stack : ptr >= fcn->bp_off;
		const char *pfx = isarg ? ARGPREFIX : VARPREFIX;
		st64 frame_off;
		if (type == R_ANAL_VAR_KIND_SPV) {
			frame_off = ptr - fcn->stack;
		} else {
			frame_off = ptr - fcn->bp_off;
		}
		if (maxstackframe != 0 && (frame_off > maxstackframe || frame_off < -maxstackframe)) {
			goto beach;
		}
		RAnalVar *var = get_stack_var (fcn, frame_off);
		if (var) {
			r_anal_var_set_access (var, reg, op->addr, rw, ptr);
			goto beach;
		}
		char *varname = NULL, *vartype = NULL;
		if (isarg) {
			const char *place = fcn->cc ? r_anal_cc_arg (anal, fcn->cc, ST32_MAX) : NULL;
			bool stack_rev = place ? !strcmp (place, "stack_rev") : false;
			char *fname = r_type_func_guess (anal->sdb_types, fcn->name);
			if (fname) {
				ut64 sum_sz = 0;
				size_t from, to, i;
				if (stack_rev) {
					const size_t cnt = r_type_func_args_count (anal->sdb_types, fname);
					from = cnt ? cnt - 1 : cnt;
					to = fcn->cc ? r_anal_cc_max_arg (anal, fcn->cc) : 0;
				} else {
					from = fcn->cc ? r_anal_cc_max_arg (anal, fcn->cc) : 0;
					to = r_type_func_args_count (anal->sdb_types, fname);
				}
				const int bytes = (fcn->bits ? fcn->bits : anal->bits) / 8;
				for (i = from; stack_rev ? i >= to : i < to; stack_rev ? i-- : i++) {
					char *tp = r_type_func_args_type (anal->sdb_types, fname, i);
					if (!tp) {
						break;
					}
					if (sum_sz == frame_off) {
						vartype = tp;
						varname = strdup (r_type_func_args_name (anal->sdb_types, fname, i));
						break;
					}
					ut64 bit_sz = r_type_get_bitsize (anal->sdb_types, tp);
					sum_sz += bit_sz ? bit_sz / 8 : bytes;
					sum_sz = R_ROUND (sum_sz, bytes);
					free (tp);
				}
				free (fname);
			}
		}
		if (!varname) {
			if (anal->opt.varname_stack) {
				varname = r_str_newf ("%s_%" PFMT64x "h", pfx, R_ABS (frame_off));
			} else {
				varname = r_anal_function_autoname_var (fcn, type, pfx, ptr);
			}
		}
		if (varname) {
#if 0
			if (isarg && frame_off > 48) {
				free (varname);
				goto beach;
			}
#endif
			RAnalVar *var = r_anal_function_set_var (fcn, frame_off, type, vartype, anal->bits / 8, isarg, varname);
			if (var) {
				r_anal_var_set_access (var, reg, op->addr, rw, ptr);
			}
			free (varname);
		}
		free (vartype);
	} else {
		st64 frame_off = -(ptr + fcn->bp_off);
		if (maxstackframe != 0 && (frame_off > maxstackframe || frame_off < -maxstackframe)) {
			goto beach;
		}
		RAnalVar *var = get_stack_var (fcn, frame_off);
		if (var) {
			r_anal_var_set_access (var, reg, op->addr, rw, -ptr);
			goto beach;
		}
		char *varname = anal->opt.varname_stack
			? r_str_newf ("%s_%" PFMT64x "h", VARPREFIX, R_ABS (frame_off))
			: r_anal_function_autoname_var (fcn, type, VARPREFIX, -ptr);
		if (varname) {
			RAnalVar *var = r_anal_function_set_var (fcn, frame_off, type, NULL, anal->bits / 8, false, varname);
			if (var) {
				r_anal_var_set_access (var, reg, op->addr, rw, -ptr);
			}
			free (varname);
		}
	}
beach:
	free (esil_buf);
}

Use After Free in op_is_set_bp in GitHub repository radareorg/radare2 prior to 5.6.6.


patch 8.2.5162: reading before the start of the line with BS in Replace mode

Problem:    Reading before the start of the line with BS in Replace mode.
Solution:   Check the cursor column is more than zero.

ins_bs(
    int		c,
    int		mode,
    int		*inserted_space_p)
{
    linenr_T	lnum;
    int		cc;
    int		temp = 0;	    // init for GCC
    colnr_T	save_col;
    colnr_T	mincol;
    int		did_backspace = FALSE;
    int		in_indent;
    int		oldState;
    int		cpc[MAX_MCO];	    // composing characters
    int		call_fix_indent = FALSE;

    /*
     * can't delete anything in an empty file
     * can't backup past first character in buffer
     * can't backup past starting point unless 'backspace' > 1
     * can backup to a previous line if 'backspace' == 0
     */
    if (       BUFEMPTY()
	    || (
#ifdef FEAT_RIGHTLEFT
		!revins_on &&
#endif
		((curwin->w_cursor.lnum == 1 && curwin->w_cursor.col == 0)
		    || (!can_bs(BS_START)
			&& ((arrow_used
#ifdef FEAT_JOB_CHANNEL
				&& !bt_prompt(curbuf)
#endif
			) || (curwin->w_cursor.lnum == Insstart_orig.lnum
				&& curwin->w_cursor.col <= Insstart_orig.col)))
		    || (!can_bs(BS_INDENT) && !arrow_used && ai_col > 0
					 && curwin->w_cursor.col <= ai_col)
		    || (!can_bs(BS_EOL) && curwin->w_cursor.col == 0))))
    {
	vim_beep(BO_BS);
	return FALSE;
    }

    if (stop_arrow() == FAIL)
	return FALSE;
    in_indent = inindent(0);
    if (in_indent)
	can_cindent = FALSE;
    end_comment_pending = NUL;	// After BS, don't auto-end comment
#ifdef FEAT_RIGHTLEFT
    if (revins_on)	    // put cursor after last inserted char
	inc_cursor();
#endif

    // Virtualedit:
    //	BACKSPACE_CHAR eats a virtual space
    //	BACKSPACE_WORD eats all coladd
    //	BACKSPACE_LINE eats all coladd and keeps going
    if (curwin->w_cursor.coladd > 0)
    {
	if (mode == BACKSPACE_CHAR)
	{
	    --curwin->w_cursor.coladd;
	    return TRUE;
	}
	if (mode == BACKSPACE_WORD)
	{
	    curwin->w_cursor.coladd = 0;
	    return TRUE;
	}
	curwin->w_cursor.coladd = 0;
    }

    /*
     * Delete newline!
     */
    if (curwin->w_cursor.col == 0)
    {
	lnum = Insstart.lnum;
	if (curwin->w_cursor.lnum == lnum
#ifdef FEAT_RIGHTLEFT
			|| revins_on
#endif
				    )
	{
	    if (u_save((linenr_T)(curwin->w_cursor.lnum - 2),
			       (linenr_T)(curwin->w_cursor.lnum + 1)) == FAIL)
		return FALSE;
	    --Insstart.lnum;
	    Insstart.col = (colnr_T)STRLEN(ml_get(Insstart.lnum));
	}
	/*
	 * In replace mode:
	 * cc < 0: NL was inserted, delete it
	 * cc >= 0: NL was replaced, put original characters back
	 */
	cc = -1;
	if (State & REPLACE_FLAG)
	    cc = replace_pop();	    // returns -1 if NL was inserted
	/*
	 * In replace mode, in the line we started replacing, we only move the
	 * cursor.
	 */
	if ((State & REPLACE_FLAG) && curwin->w_cursor.lnum <= lnum)
	{
	    dec_cursor();
	}
	else
	{
	    if (!(State & VREPLACE_FLAG)
				   || curwin->w_cursor.lnum > orig_line_count)
	    {
		temp = gchar_cursor();	// remember current char
		--curwin->w_cursor.lnum;

		// When "aw" is in 'formatoptions' we must delete the space at
		// the end of the line, otherwise the line will be broken
		// again when auto-formatting.
		if (has_format_option(FO_AUTO)
					   && has_format_option(FO_WHITE_PAR))
		{
		    char_u  *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum,
									TRUE);
		    int	    len;

		    len = (int)STRLEN(ptr);
		    if (len > 0 && ptr[len - 1] == ' ')
			ptr[len - 1] = NUL;
		}

		(void)do_join(2, FALSE, FALSE, FALSE, FALSE);
		if (temp == NUL && gchar_cursor() != NUL)
		    inc_cursor();
	    }
	    else
		dec_cursor();

	    /*
	     * In MODE_REPLACE mode we have to put back the text that was
	     * replaced by the NL. On the replace stack is first a
	     * NUL-terminated sequence of characters that were deleted and then
	     * the characters that NL replaced.
	     */
	    if (State & REPLACE_FLAG)
	    {
		/*
		 * Do the next ins_char() in MODE_NORMAL state, to
		 * prevent ins_char() from replacing characters and
		 * avoiding showmatch().
		 */
		oldState = State;
		State = MODE_NORMAL;
		/*
		 * restore characters (blanks) deleted after cursor
		 */
		while (cc > 0)
		{
		    save_col = curwin->w_cursor.col;
		    mb_replace_pop_ins(cc);
		    curwin->w_cursor.col = save_col;
		    cc = replace_pop();
		}
		// restore the characters that NL replaced
		replace_pop_ins();
		State = oldState;
	    }
	}
	did_ai = FALSE;
    }
    else
    {
	/*
	 * Delete character(s) before the cursor.
	 */
#ifdef FEAT_RIGHTLEFT
	if (revins_on)		// put cursor on last inserted char
	    dec_cursor();
#endif
	mincol = 0;
						// keep indent
	if (mode == BACKSPACE_LINE
		&& (curbuf->b_p_ai || cindent_on())
#ifdef FEAT_RIGHTLEFT
		&& !revins_on
#endif
			    )
	{
	    save_col = curwin->w_cursor.col;
	    beginline(BL_WHITE);
	    if (curwin->w_cursor.col < save_col)
	    {
		mincol = curwin->w_cursor.col;
		// should now fix the indent to match with the previous line
		call_fix_indent = TRUE;
	    }
	    curwin->w_cursor.col = save_col;
	}

	/*
	 * Handle deleting one 'shiftwidth' or 'softtabstop'.
	 */
	if (	   mode == BACKSPACE_CHAR
		&& ((p_sta && in_indent)
		    || ((get_sts_value() != 0
#ifdef FEAT_VARTABS
			|| tabstop_count(curbuf->b_p_vsts_array)
#endif
			)
			&& curwin->w_cursor.col > 0
			&& (*(ml_get_cursor() - 1) == TAB
			    || (*(ml_get_cursor() - 1) == ' '
				&& (!*inserted_space_p
				    || arrow_used))))))
	{
	    int		ts;
	    colnr_T	vcol;
	    colnr_T	want_vcol;
	    colnr_T	start_vcol;

	    *inserted_space_p = FALSE;
	    // Compute the virtual column where we want to be.  Since
	    // 'showbreak' may get in the way, need to get the last column of
	    // the previous character.
	    getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
	    start_vcol = vcol;
	    dec_cursor();
	    getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol);
	    inc_cursor();
#ifdef FEAT_VARTABS
	    if (p_sta && in_indent)
	    {
		ts = (int)get_sw_value(curbuf);
		want_vcol = (want_vcol / ts) * ts;
	    }
	    else
		want_vcol = tabstop_start(want_vcol, get_sts_value(),
						       curbuf->b_p_vsts_array);
#else
	    if (p_sta && in_indent)
		ts = (int)get_sw_value(curbuf);
	    else
		ts = (int)get_sts_value();
	    want_vcol = (want_vcol / ts) * ts;
#endif

	    // delete characters until we are at or before want_vcol
	    while (vcol > want_vcol
		    && (cc = *(ml_get_cursor() - 1), VIM_ISWHITE(cc)))
		ins_bs_one(&vcol);

	    // insert extra spaces until we are at want_vcol
	    while (vcol < want_vcol)
	    {
		// Remember the first char we inserted
		if (curwin->w_cursor.lnum == Insstart_orig.lnum
				   && curwin->w_cursor.col < Insstart_orig.col)
		    Insstart_orig.col = curwin->w_cursor.col;

		if (State & VREPLACE_FLAG)
		    ins_char(' ');
		else
		{
		    ins_str((char_u *)" ");
		    if ((State & REPLACE_FLAG))
			replace_push(NUL);
		}
		getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
	    }

	    // If we are now back where we started delete one character.  Can
	    // happen when using 'sts' and 'linebreak'.
	    if (vcol >= start_vcol)
		ins_bs_one(&vcol);
	}

	/*
	 * Delete up to starting point, start of line or previous word.
	 */
	else
	{
	    int cclass = 0, prev_cclass = 0;

	    if (has_mbyte)
		cclass = mb_get_class(ml_get_cursor());
	    do
	    {
#ifdef FEAT_RIGHTLEFT
		if (!revins_on) // put cursor on char to be deleted
#endif
		    dec_cursor();

		cc = gchar_cursor();
		// look multi-byte character class
		if (has_mbyte)
		{
		    prev_cclass = cclass;
		    cclass = mb_get_class(ml_get_cursor());
		}

		// start of word?
		if (mode == BACKSPACE_WORD && !vim_isspace(cc))
		{
		    mode = BACKSPACE_WORD_NOT_SPACE;
		    temp = vim_iswordc(cc);
		}
		// end of word?
		else if (mode == BACKSPACE_WORD_NOT_SPACE
			&& ((vim_isspace(cc) || vim_iswordc(cc) != temp)
			|| prev_cclass != cclass))
		{
#ifdef FEAT_RIGHTLEFT
		    if (!revins_on)
#endif
			inc_cursor();
#ifdef FEAT_RIGHTLEFT
		    else if (State & REPLACE_FLAG)
			dec_cursor();
#endif
		    break;
		}
		if (State & REPLACE_FLAG)
		    replace_do_bs(-1);
		else
		{
		    if (enc_utf8 && p_deco)
			(void)utfc_ptr2char(ml_get_cursor(), cpc);
		    (void)del_char(FALSE);
		    /*
		     * If there are combining characters and 'delcombine' is set
		     * move the cursor back.  Don't back up before the base
		     * character.
		     */
		    if (enc_utf8 && p_deco && cpc[0] != NUL)
			inc_cursor();
#ifdef FEAT_RIGHTLEFT
		    if (revins_chars)
		    {
			revins_chars--;
			revins_legal++;
		    }
		    if (revins_on && gchar_cursor() == NUL)
			break;
#endif
		}
		// Just a single backspace?:
		if (mode == BACKSPACE_CHAR)
		    break;
	    } while (
#ifdef FEAT_RIGHTLEFT
		    revins_on ||
#endif
		    (curwin->w_cursor.col > mincol
		    &&  (can_bs(BS_NOSTOP)
			|| (curwin->w_cursor.lnum != Insstart_orig.lnum
			|| curwin->w_cursor.col != Insstart_orig.col)
		    )));
	}
	did_backspace = TRUE;
    }
    did_si = FALSE;
    can_si = FALSE;
    can_si_back = FALSE;
    if (curwin->w_cursor.col <= 1)
	did_ai = FALSE;

    if (call_fix_indent)
	fix_indent();

    /*
     * It's a little strange to put backspaces into the redo
     * buffer, but it makes auto-indent a lot easier to deal
     * with.
     */
    AppendCharToRedobuff(c);

    // If deleted before the insertion point, adjust it
    if (curwin->w_cursor.lnum == Insstart_orig.lnum
				  && curwin->w_cursor.col < Insstart_orig.col)
	Insstart_orig.col = curwin->w_cursor.col;

    // vi behaviour: the cursor moves backward but the character that
    //		     was there remains visible
    // Vim behaviour: the cursor moves backward and the character that
    //		      was there is erased from the screen.
    // We can emulate the vi behaviour by pretending there is a dollar
    // displayed even when there isn't.
    //  --pkv Sun Jan 19 01:56:40 EST 2003
    if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1)
	dollar_vcol = curwin->w_virtcol;

#ifdef FEAT_FOLDING
    // When deleting a char the cursor line must never be in a closed fold.
    // E.g., when 'foldmethod' is indent and deleting the first non-white
    // char before a Tab.
    if (did_backspace)
	foldOpenCursor();
#endif

    return did_backspace;
}

Heap-based Buffer Overflow in GitHub repository vim/vim prior to 8.2.


udf: Fix NULL ptr deref when converting from inline format

udf_expand_file_adinicb() calls directly ->writepage to write data
expanded into a page. This however misses to setup inode for writeback
properly and so we can crash on inode->i_wb dereference when submitting
page for IO like:

  BUG: kernel NULL pointer dereference, address: 0000000000000158
  #PF: supervisor read access in kernel mode
...
  <TASK>
  __folio_start_writeback+0x2ac/0x350
  __block_write_full_page+0x37d/0x490
  udf_expand_file_adinicb+0x255/0x400 [udf]
  udf_file_write_iter+0xbe/0x1b0 [udf]
  new_sync_write+0x125/0x1c0
  vfs_write+0x28e/0x400

Fix the problem by marking the page dirty and going through the standard
writeback path to write the page. Strictly speaking we would not even
have to write the page but we want to catch e.g. ENOSPC errors early.

Reported-by: butt3rflyh4ck <butterflyhuangxx@gmail.com>
CC: stable@vger.kernel.org
Fixes: 52ebea749aae ("writeback: make backing_dev_info host cgroup-specific bdi_writebacks")
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Jan Kara <jack@suse.cz>

int udf_expand_file_adinicb(struct inode *inode)
{
	struct page *page;
	char *kaddr;
	struct udf_inode_info *iinfo = UDF_I(inode);
	int err;
	struct writeback_control udf_wbc = {
		.sync_mode = WB_SYNC_NONE,
		.nr_to_write = 1,
	};

	WARN_ON_ONCE(!inode_is_locked(inode));
	if (!iinfo->i_lenAlloc) {
		if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD))
			iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT;
		else
			iinfo->i_alloc_type = ICBTAG_FLAG_AD_LONG;
		/* from now on we have normal address_space methods */
		inode->i_data.a_ops = &udf_aops;
		up_write(&iinfo->i_data_sem);
		mark_inode_dirty(inode);
		return 0;
	}
	/*
	 * Release i_data_sem so that we can lock a page - page lock ranks
	 * above i_data_sem. i_mutex still protects us against file changes.
	 */
	up_write(&iinfo->i_data_sem);

	page = find_or_create_page(inode->i_mapping, 0, GFP_NOFS);
	if (!page)
		return -ENOMEM;

	if (!PageUptodate(page)) {
		kaddr = kmap_atomic(page);
		memset(kaddr + iinfo->i_lenAlloc, 0x00,
		       PAGE_SIZE - iinfo->i_lenAlloc);
		memcpy(kaddr, iinfo->i_data + iinfo->i_lenEAttr,
			iinfo->i_lenAlloc);
		flush_dcache_page(page);
		SetPageUptodate(page);
		kunmap_atomic(kaddr);
	}
	down_write(&iinfo->i_data_sem);
	memset(iinfo->i_data + iinfo->i_lenEAttr, 0x00,
	       iinfo->i_lenAlloc);
	iinfo->i_lenAlloc = 0;
	if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD))
		iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT;
	else
		iinfo->i_alloc_type = ICBTAG_FLAG_AD_LONG;
	/* from now on we have normal address_space methods */
	inode->i_data.a_ops = &udf_aops;
	up_write(&iinfo->i_data_sem);
	err = inode->i_data.a_ops->writepage(page, &udf_wbc);
	if (err) {
		/* Restore everything back so that we don't lose data... */
		lock_page(page);
		down_write(&iinfo->i_data_sem);
		kaddr = kmap_atomic(page);
		memcpy(iinfo->i_data + iinfo->i_lenEAttr, kaddr, inode->i_size);
		kunmap_atomic(kaddr);
		unlock_page(page);
		iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB;
		inode->i_data.a_ops = &udf_adinicb_aops;
		up_write(&iinfo->i_data_sem);
	}
	put_page(page);
	mark_inode_dirty(inode);

	return err;
}

A flaw null pointer dereference in the Linux kernel UDF file system functionality was found in the way user triggers udf_file_write_iter function for the malicious UDF image. A local user could use this flaw to crash the system. Actual from Linux kernel 4.2-rc1 till 5.17-rc2.


Fix #73280: Stack Buffer Overflow in GD dynamicGetbuf

We make sure to never pass a negative `rlen` as size to memcpy().

Cf. <https://github.com/libgd/libgd/commit/53110871>.

static int dynamicGetbuf (gdIOCtxPtr ctx, void *buf, int len)
{
	int rlen, remain;
	dpIOCtxPtr dctx;
	dynamicPtr *dp;

	dctx = (dpIOCtxPtr) ctx;
	dp = dctx->dp;

	remain = dp->logicalSize - dp->pos;
	if (remain >= len) {
		rlen = len;
	} else {
		if (remain == 0) {
			return EOF;
		}
		rlen = remain;
	}

	memcpy(buf, (void *) ((char *) dp->data + dp->pos), rlen);
	dp->pos += rlen;

	return rlen;
}

Integer signedness error in the dynamicGetbuf function in gd_io_dp.c in the GD Graphics Library (aka libgd) through 2.2.3, as used in PHP before 5.6.28 and 7.x before 7.0.13, allows remote attackers to cause a denial of service (stack-based buffer overflow) or possibly have unspecified other impact via a crafted imagecreatefromstring call.


Fixed bug #22965 (Crash in gd lib's ImageFillToBorder()).

gdImageFillToBorder (gdImagePtr im, int x, int y, int border, int color)
{
  int lastBorder;
  /* Seek left */
  int leftLimit, rightLimit;
  int i;
  leftLimit = (-1);
  if (border < 0)
    {
      /* Refuse to fill to a non-solid border */
      return;
    }
  for (i = x; (i >= 0); i--)
    {
      if (gdImageGetPixel (im, i, y) == border)
	{
	  break;
	}
      gdImageSetPixel (im, i, y, color);
      leftLimit = i;
    }
  if (leftLimit == (-1))
    {
      return;
    }
  /* Seek right */
  rightLimit = x;
  for (i = (x + 1); (i < im->sx); i++)
    {
      if (gdImageGetPixel (im, i, y) == border)
	{
	  break;
	}
      gdImageSetPixel (im, i, y, color);
      rightLimit = i;
    }
  /* Look at lines above and below and start paints */
  /* Above */
  if (y > 0)
    {
      lastBorder = 1;
      for (i = leftLimit; (i <= rightLimit); i++)
	{
	  int c;
	  c = gdImageGetPixel (im, i, y - 1);
	  if (lastBorder)
	    {
	      if ((c != border) && (c != color))
		{
		  gdImageFillToBorder (im, i, y - 1,
				       border, color);
		  lastBorder = 0;
		}
	    }
	  else if ((c == border) || (c == color))
	    {
	      lastBorder = 1;
	    }
	}
    }
  /* Below */
  if (y < ((im->sy) - 1))
    {
      lastBorder = 1;
      for (i = leftLimit; (i <= rightLimit); i++)
	{
	  int c;
	  c = gdImageGetPixel (im, i, y + 1);
	  if (lastBorder)
	    {
	      if ((c != border) && (c != color))
		{
		  gdImageFillToBorder (im, i, y + 1,
				       border, color);
		  lastBorder = 0;
		}
	    }
	  else if ((c == border) || (c == color))
	    {
	      lastBorder = 1;
	    }
	}
    }
}

Stack consumption vulnerability in GD in PHP before 5.6.12 allows remote attackers to cause a denial of service via a crafted imagefilltoborder call.


Calcualte max nspace declarations correctly for XML writer

(raptor_xml_writer_start_element_common): Calculate max including for
each attribute a potential name and value.

Fixes Issues #0000617 http://bugs.librdf.org/mantis/view.php?id=617
and #0000618 http://bugs.librdf.org/mantis/view.php?id=618

raptor_xml_writer_start_element_common(raptor_xml_writer* xml_writer,
                                       raptor_xml_element* element,
                                       int auto_empty)
{
  raptor_iostream* iostr = xml_writer->iostr;
  raptor_namespace_stack *nstack = xml_writer->nstack;
  int depth = xml_writer->depth;
  int auto_indent = XML_WRITER_AUTO_INDENT(xml_writer);
  struct nsd *nspace_declarations = NULL;
  size_t nspace_declarations_count = 0;
  unsigned int i;

  /* max is 1 per element and 1 for each attribute + size of declared */
  if(nstack) {
    int nspace_max_count = element->attribute_count+1;
    if(element->declared_nspaces)
      nspace_max_count += raptor_sequence_size(element->declared_nspaces);
    if(element->xml_language)
      nspace_max_count++;

    nspace_declarations = RAPTOR_CALLOC(struct nsd*, nspace_max_count,
                                        sizeof(struct nsd));
    if(!nspace_declarations)
      return 1;
  }

  if(element->name->nspace) {
    if(nstack && !raptor_namespaces_namespace_in_scope(nstack, element->name->nspace)) {
      nspace_declarations[0].declaration=
        raptor_namespace_format_as_xml(element->name->nspace,
                                       &nspace_declarations[0].length);
      if(!nspace_declarations[0].declaration)
        goto error;
      nspace_declarations[0].nspace = element->name->nspace;
      nspace_declarations_count++;
    }
  }

  if(nstack && element->attributes) {
    for(i = 0; i < element->attribute_count; i++) {
      /* qname */
      if(element->attributes[i]->nspace) {
        /* Check if we need a namespace declaration attribute */
        if(nstack &&
           !raptor_namespaces_namespace_in_scope(nstack, element->attributes[i]->nspace) && element->attributes[i]->nspace != element->name->nspace) {
          /* not in scope and not same as element (so already going to be declared)*/
          unsigned int j;
          int declare_me = 1;

          /* check it wasn't an earlier declaration too */
          for(j = 0; j < nspace_declarations_count; j++)
            if(nspace_declarations[j].nspace == element->attributes[j]->nspace) {
              declare_me = 0;
              break;
            }

          if(declare_me) {
            nspace_declarations[nspace_declarations_count].declaration=
              raptor_namespace_format_as_xml(element->attributes[i]->nspace,
                                             &nspace_declarations[nspace_declarations_count].length);
            if(!nspace_declarations[nspace_declarations_count].declaration)
              goto error;
            nspace_declarations[nspace_declarations_count].nspace = element->attributes[i]->nspace;
            nspace_declarations_count++;
          }
        }
      }

      /* Add the attribute + value */
      nspace_declarations[nspace_declarations_count].declaration=
        raptor_qname_format_as_xml(element->attributes[i],
                                   &nspace_declarations[nspace_declarations_count].length);
      if(!nspace_declarations[nspace_declarations_count].declaration)
        goto error;
      nspace_declarations[nspace_declarations_count].nspace = NULL;
      nspace_declarations_count++;

    }
  }

  if(nstack && element->declared_nspaces &&
     raptor_sequence_size(element->declared_nspaces) > 0) {
    for(i = 0; i< (unsigned int)raptor_sequence_size(element->declared_nspaces); i++) {
      raptor_namespace* nspace = (raptor_namespace*)raptor_sequence_get_at(element->declared_nspaces, i);
      unsigned int j;
      int declare_me = 1;

      /* check it wasn't an earlier declaration too */
      for(j = 0; j < nspace_declarations_count; j++)
        if(nspace_declarations[j].nspace == nspace) {
          declare_me = 0;
          break;
        }

      if(declare_me) {
        nspace_declarations[nspace_declarations_count].declaration=
          raptor_namespace_format_as_xml(nspace,
                                         &nspace_declarations[nspace_declarations_count].length);
        if(!nspace_declarations[nspace_declarations_count].declaration)
          goto error;
        nspace_declarations[nspace_declarations_count].nspace = nspace;
        nspace_declarations_count++;
      }

    }
  }

  if(nstack && element->xml_language) {
    size_t lang_len = strlen(RAPTOR_GOOD_CAST(char*, element->xml_language));
#define XML_LANG_PREFIX_LEN 10
    size_t buf_length = XML_LANG_PREFIX_LEN + lang_len + 1;
    unsigned char* buffer = RAPTOR_MALLOC(unsigned char*, buf_length + 1);
    const char quote = '\"';
    unsigned char* p;

    memcpy(buffer, "xml:lang=\"", XML_LANG_PREFIX_LEN);
    p = buffer + XML_LANG_PREFIX_LEN;
    p += raptor_xml_escape_string(xml_writer->world,
                                  element->xml_language, lang_len,
                                  p, buf_length, quote);
    *p++ = quote;
    *p = '\0';

    nspace_declarations[nspace_declarations_count].declaration = buffer;
    nspace_declarations[nspace_declarations_count].length = buf_length;
    nspace_declarations[nspace_declarations_count].nspace = NULL;
    nspace_declarations_count++;
  }

  raptor_iostream_write_byte('<', iostr);

  if(element->name->nspace && element->name->nspace->prefix_length > 0) {
    raptor_iostream_counted_string_write((const char*)element->name->nspace->prefix,
                                         element->name->nspace->prefix_length,
                                         iostr);
    raptor_iostream_write_byte(':', iostr);
  }
  raptor_iostream_counted_string_write((const char*)element->name->local_name,
                                       element->name->local_name_length,
                                       iostr);

  /* declare namespaces and attributes */
  if(nspace_declarations_count) {
    int need_indent = 0;

    /* sort them into the canonical order */
    qsort((void*)nspace_declarations,
          nspace_declarations_count, sizeof(struct nsd),
          raptor_xml_writer_nsd_compare);

    /* declare namespaces first */
    for(i = 0; i < nspace_declarations_count; i++) {
      if(!nspace_declarations[i].nspace)
        continue;

      if(auto_indent && need_indent) {
        /* indent attributes */
        raptor_xml_writer_newline(xml_writer);
        xml_writer->depth++;
        raptor_xml_writer_indent(xml_writer);
        xml_writer->depth--;
      }
      raptor_iostream_write_byte(' ', iostr);
      raptor_iostream_counted_string_write((const char*)nspace_declarations[i].declaration,
                                           nspace_declarations[i].length,
                                           iostr);
      RAPTOR_FREE(char*, nspace_declarations[i].declaration);
      nspace_declarations[i].declaration = NULL;
      need_indent = 1;

      if(raptor_namespace_stack_start_namespace(nstack,
                                                (raptor_namespace*)nspace_declarations[i].nspace,
                                                depth))
        goto error;
    }

    /* declare attributes */
    for(i = 0; i < nspace_declarations_count; i++) {
      if(nspace_declarations[i].nspace)
        continue;

      if(auto_indent && need_indent) {
        /* indent attributes */
        raptor_xml_writer_newline(xml_writer);
        xml_writer->depth++;
        raptor_xml_writer_indent(xml_writer);
        xml_writer->depth--;
      }
      raptor_iostream_write_byte(' ', iostr);
      raptor_iostream_counted_string_write((const char*)nspace_declarations[i].declaration,
                                           nspace_declarations[i].length,
                                           iostr);
      need_indent = 1;

      RAPTOR_FREE(char*, nspace_declarations[i].declaration);
      nspace_declarations[i].declaration = NULL;
    }
  }

  if(!auto_empty)
    raptor_iostream_write_byte('>', iostr);

  if(nstack)
    RAPTOR_FREE(stringarray, nspace_declarations);

  return 0;

  /* Clean up nspace_declarations on error */
  error:

  for(i = 0; i < nspace_declarations_count; i++) {
    if(nspace_declarations[i].declaration)
      RAPTOR_FREE(char*, nspace_declarations[i].declaration);
  }

  RAPTOR_FREE(stringarray, nspace_declarations);

  return 1;
}

raptor_xml_writer_start_element_common in raptor_xml_writer.c in Raptor RDF Syntax Library 2.0.15 miscalculates the maximum nspace declarations for the XML writer, leading to heap-based buffer overflows (sometimes seen in raptor_qname_format_as_xml).


netfilter: nf_tables: disallow binding to already bound chain

Update nft_data_init() to report EINVAL if chain is already bound.

Fixes: d0e2c7de92c7 ("netfilter: nf_tables: add NFT_CHAIN_BINDING")
Reported-by: Gwangun Jung <exsociety@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,
			    struct nft_data_desc *desc, const struct nlattr *nla)
{
	u8 genmask = nft_genmask_next(ctx->net);
	struct nlattr *tb[NFTA_VERDICT_MAX + 1];
	struct nft_chain *chain;
	int err;

	err = nla_parse_nested_deprecated(tb, NFTA_VERDICT_MAX, nla,
					  nft_verdict_policy, NULL);
	if (err < 0)
		return err;

	if (!tb[NFTA_VERDICT_CODE])
		return -EINVAL;
	data->verdict.code = ntohl(nla_get_be32(tb[NFTA_VERDICT_CODE]));

	switch (data->verdict.code) {
	default:
		switch (data->verdict.code & NF_VERDICT_MASK) {
		case NF_ACCEPT:
		case NF_DROP:
		case NF_QUEUE:
			break;
		default:
			return -EINVAL;
		}
		fallthrough;
	case NFT_CONTINUE:
	case NFT_BREAK:
	case NFT_RETURN:
		break;
	case NFT_JUMP:
	case NFT_GOTO:
		if (tb[NFTA_VERDICT_CHAIN]) {
			chain = nft_chain_lookup(ctx->net, ctx->table,
						 tb[NFTA_VERDICT_CHAIN],
						 genmask);
		} else if (tb[NFTA_VERDICT_CHAIN_ID]) {
			chain = nft_chain_lookup_byid(ctx->net, ctx->table,
						      tb[NFTA_VERDICT_CHAIN_ID]);
			if (IS_ERR(chain))
				return PTR_ERR(chain);
		} else {
			return -EINVAL;
		}

		if (IS_ERR(chain))
			return PTR_ERR(chain);
		if (nft_is_base_chain(chain))
			return -EOPNOTSUPP;
		if (desc->flags & NFT_DATA_DESC_SETELEM &&
		    chain->flags & NFT_CHAIN_BINDING)
			return -EINVAL;

		chain->use++;
		data->verdict.chain = chain;
		break;
	}

	desc->len = sizeof(data->verdict);

	return 0;
}

An issue was discovered in net/netfilter/nf_tables_api.c in the Linux kernel before 5.19.6. A denial of service can occur upon binding to an already bound chain.


Fix oobread in the macho parser ##crash

* Reported by @Han0nly via huntr.dev
* Reproducers: heapoverflow1
* BountyID: e589bd97-4c74-4e79-93b5-0951a281facc

static void parse_relocation_info(struct MACH0_(obj_t) *bin, RSkipList *relocs, ut32 offset, ut32 num) {
	if (!num || !offset || (st32)num < 0) {
		return;
	}

	ut64 total_size = num * sizeof (struct relocation_info);
	if (offset > bin->size) {
		return;
	}
	if (total_size > bin->size) {
		total_size = bin->size - offset;
		num = total_size /= sizeof (struct relocation_info);
	}
	struct relocation_info *info = calloc (num, sizeof (struct relocation_info));
	if (!info) {
		return;
	}

	if (r_buf_read_at (bin->b, offset, (ut8 *) info, total_size) < total_size) {
		free (info);
		return;
	}

	size_t i;
	for (i = 0; i < num; i++) {
		struct relocation_info a_info = info[i];
		ut32 sym_num = a_info.r_symbolnum;
		if (sym_num > bin->nsymtab) {
			continue;
		}

		ut32 stridx = bin->symtab[sym_num].n_strx;
		char *sym_name = get_name (bin, stridx, false);
		if (!sym_name) {
			continue;
		}

		struct reloc_t *reloc = R_NEW0 (struct reloc_t);
		if (!reloc) {
			free (info);
			free (sym_name);
			return;
		}

		reloc->addr = offset_to_vaddr (bin, a_info.r_address);
		reloc->offset = a_info.r_address;
		reloc->ord = sym_num;
		reloc->type = a_info.r_type; // enum RelocationInfoType
		reloc->external = a_info.r_extern;
		reloc->pc_relative = a_info.r_pcrel;
		reloc->size = a_info.r_length;
		r_str_ncpy (reloc->name, sym_name, sizeof (reloc->name) - 1);
		r_skiplist_insert (relocs, reloc);
		free (sym_name);
	}
	free (info);
}

Heap buffer overflow in libr/bin/format/mach0/mach0.c in GitHub repository radareorg/radare2 prior to 5.8.6. If address sanitizer is disabled during the compiling, the program should executes into the `r_str_ncpy` function. Therefore I think it is very likely to be exploitable. For more general description of heap buffer overflow, see [CWE](https://cwe.mitre.org/data/definitions/122.html).


server: reset meta context replies on starttls

Related to CVE-2021-3716, but not as severe.  No compliant client will
send NBD_CMD_BLOCK_STATUS unless it first negotiates
NBD_OPT_SET_META_CONTEXT.  If an attacker injects a premature
SET_META_CONTEXT, either the client will never notice (because it
never uses BLOCK_STATUS), or the client will overwrite the attacker's
attempt with the client's own SET_META_CONTEXT request after
encryption is enabled.  So I don't class this as having the potential
to trigger denial-of-service due to any protocol mismatch between
compliant client and server (I don't care what happens with
non-compliant clients).

Fixes: 26455d45 (server: protocol: Implement Block Status "base:allocation".)

negotiate_handshake_newstyle_options (void)
{
  GET_CONN;
  struct nbd_new_option new_option;
  size_t nr_options;
  bool list_seen = false;
  uint64_t version;
  uint32_t option;
  uint32_t optlen;
  struct nbd_export_name_option_reply handshake_finish;
  const char *optname;
  uint64_t exportsize;
  struct backend *b;

  for (nr_options = MAX_NR_OPTIONS; nr_options > 0; --nr_options) {
    CLEANUP_FREE char *data = NULL;

    if (conn_recv_full (&new_option, sizeof new_option,
                        "reading option: conn->recv: %m") == -1)
      return -1;

    version = be64toh (new_option.version);
    if (version != NBD_NEW_VERSION) {
      nbdkit_error ("unknown option version %" PRIx64
                    ", expecting %" PRIx64,
                    version, NBD_NEW_VERSION);
      return -1;
    }

    /* There is a maximum option length we will accept, regardless
     * of the option type.
     */
    optlen = be32toh (new_option.optlen);
    if (optlen > MAX_REQUEST_SIZE) {
      nbdkit_error ("client option data too long (%" PRIu32 ")", optlen);
      return -1;
    }
    data = malloc (optlen + 1); /* Allowing a trailing NUL helps some uses */
    if (data == NULL) {
      nbdkit_error ("malloc: %m");
      return -1;
    }

    option = be32toh (new_option.option);
    optname = name_of_nbd_opt (option);

    /* If the client lacks fixed newstyle support, it should only send
     * NBD_OPT_EXPORT_NAME.
     */
    if (!(conn->cflags & NBD_FLAG_FIXED_NEWSTYLE) &&
        option != NBD_OPT_EXPORT_NAME) {
      if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID))
        return -1;
      continue;
    }

    /* In --tls=require / FORCEDTLS mode the only options allowed
     * before TLS negotiation are NBD_OPT_ABORT and NBD_OPT_STARTTLS.
     */
    if (tls == 2 && !conn->using_tls &&
        !(option == NBD_OPT_ABORT || option == NBD_OPT_STARTTLS)) {
      if (send_newstyle_option_reply (option, NBD_REP_ERR_TLS_REQD))
        return -1;
      continue;
    }

    switch (option) {
    case NBD_OPT_EXPORT_NAME:
      if (conn_recv_full (data, optlen,
                          "read: %s: %m", name_of_nbd_opt (option)) == -1)
        return -1;
      if (check_export_name (option, data, optlen, optlen) == -1)
        return -1;

      /* We have to finish the handshake by sending handshake_finish.
       * On failure, we have to disconnect.
       */
      if (finish_newstyle_options (&exportsize, data, optlen) == -1)
        return -1;

      memset (&handshake_finish, 0, sizeof handshake_finish);
      handshake_finish.exportsize = htobe64 (exportsize);
      handshake_finish.eflags = htobe16 (conn->eflags);

      if (conn->send (&handshake_finish,
                      (conn->cflags & NBD_FLAG_NO_ZEROES)
                      ? offsetof (struct nbd_export_name_option_reply, zeroes)
                      : sizeof handshake_finish, 0) == -1) {
        nbdkit_error ("write: %s: %m", optname);
        return -1;
      }
      break;

    case NBD_OPT_ABORT:
      if (send_newstyle_option_reply (option, NBD_REP_ACK) == -1)
        return -1;
      debug ("client sent %s to abort the connection",
             name_of_nbd_opt (option));
      return -1;

    case NBD_OPT_LIST:
      if (optlen != 0) {
        if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID)
            == -1)
          return -1;
        if (conn_recv_full (data, optlen,
                            "read: %s: %m", name_of_nbd_opt (option)) == -1)
          return -1;
        continue;
      }

      if (list_seen) {
        debug ("newstyle negotiation: %s: export list already advertised",
               name_of_nbd_opt (option));
        if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID) == -1)
          return -1;
        continue;
      }
      else {
        /* Send back the exportname list. */
        debug ("newstyle negotiation: %s: advertising exports",
               name_of_nbd_opt (option));
        if (send_newstyle_option_reply_exportnames (option, &nr_options) == -1)
          return -1;
        list_seen = true;
      }
      break;

    case NBD_OPT_STARTTLS:
      if (optlen != 0) {
        if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID)
            == -1)
          return -1;
        if (conn_recv_full (data, optlen,
                            "read: %s: %m", name_of_nbd_opt (option)) == -1)
          return -1;
        continue;
      }

      if (tls == 0) {           /* --tls=off (NOTLS mode). */
#ifdef HAVE_GNUTLS
#define NO_TLS_REPLY NBD_REP_ERR_POLICY
#else
#define NO_TLS_REPLY NBD_REP_ERR_UNSUP
#endif
        if (send_newstyle_option_reply (option, NO_TLS_REPLY) == -1)
          return -1;
      }
      else /* --tls=on or --tls=require */ {
        /* We can't upgrade to TLS twice on the same connection. */
        if (conn->using_tls) {
          if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID) == -1)
            return -1;
          continue;
        }

        /* We have to send the (unencrypted) reply before starting
         * the handshake.
         */
        if (send_newstyle_option_reply (option, NBD_REP_ACK) == -1)
          return -1;

        /* Upgrade the connection to TLS.  Also performs access control. */
        if (crypto_negotiate_tls (conn->sockin, conn->sockout) == -1)
          return -1;
        conn->using_tls = true;
        debug ("using TLS on this connection");
        /* Wipe out any cached state. */
        conn->structured_replies = false;
        for_each_backend (b) {
          free (conn->default_exportname[b->i]);
          conn->default_exportname[b->i] = NULL;
        }
      }
      break;

    case NBD_OPT_INFO:
    case NBD_OPT_GO:
      if (conn_recv_full (data, optlen, "read: %s: %m", optname) == -1)
        return -1;

      if (optlen < 6) { /* 32 bit export length + 16 bit nr info */
        debug ("newstyle negotiation: %s option length < 6", optname);

        if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID)
            == -1)
          return -1;
        continue;
      }

      {
        uint32_t exportnamelen;
        uint16_t nrinfos;
        uint16_t info;
        size_t i;

        /* Validate the name length and number of INFO requests. */
        memcpy (&exportnamelen, &data[0], 4);
        exportnamelen = be32toh (exportnamelen);
        if (exportnamelen > optlen-6 /* NB optlen >= 6, see above */) {
          debug ("newstyle negotiation: %s: export name too long", optname);
          if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID)
              == -1)
            return -1;
          continue;
        }
        memcpy (&nrinfos, &data[exportnamelen+4], 2);
        nrinfos = be16toh (nrinfos);
        if (optlen != 4 + exportnamelen + 2 + 2*nrinfos) {
          debug ("newstyle negotiation: %s: "
                 "number of information requests incorrect", optname);
          if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID)
              == -1)
            return -1;
          continue;
        }

        /* As with NBD_OPT_EXPORT_NAME we print the export name and
         * save it in the connection.  If an earlier
         * NBD_OPT_SET_META_CONTEXT used an export name, it must match
         * or else we drop the support for that context.
         */
        if (check_export_name (option, &data[4], exportnamelen,
                               optlen - 6) == -1) {
          if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID)
              == -1)
            return -1;
          continue;
        }

        /* The spec is confusing, but it is required that we send back
         * NBD_INFO_EXPORT, even if the client did not request it!
         * qemu client in particular does not request this, but will
         * fail if we don't send it.  Note that if .open fails, but we
         * succeed at .close, then we merely return an error to the
         * client and let them try another NBD_OPT, rather than
         * disconnecting.
         */
        if (finish_newstyle_options (&exportsize,
                                     &data[4], exportnamelen) == -1) {
          if (conn->top_context) {
            if (backend_finalize (conn->top_context) == -1)
              return -1;
            backend_close (conn->top_context);
            conn->top_context = NULL;
          }
          if (send_newstyle_option_reply (option, NBD_REP_ERR_UNKNOWN) == -1)
            return -1;
          continue;
        }

        if (send_newstyle_option_reply_info_export (option,
                                                    NBD_REP_INFO,
                                                    NBD_INFO_EXPORT,
                                                    exportsize) == -1)
          return -1;

        /* For now we send NBD_INFO_NAME and NBD_INFO_DESCRIPTION if
         * requested, and ignore all other info requests (including
         * NBD_INFO_EXPORT if it was requested, because we replied
         * already above).
         */
        for (i = 0; i < nrinfos; ++i) {
          memcpy (&info, &data[4 + exportnamelen + 2 + i*2], 2);
          info = be16toh (info);
          switch (info) {
          case NBD_INFO_EXPORT: /* ignore - reply sent above */ break;
          case NBD_INFO_NAME:
            {
              const char *name = &data[4];
              size_t namelen = exportnamelen;

              if (exportnamelen == 0) {
                name = backend_default_export (top, read_only);
                if (!name) {
                  debug ("newstyle negotiation: %s: "
                         "NBD_INFO_NAME: no name to send", optname);
                  break;
                }
                namelen = -1;
              }
              if (send_newstyle_option_reply_info_str (option,
                                                       NBD_REP_INFO,
                                                       NBD_INFO_NAME,
                                                       name, namelen) == -1)
                return -1;
            }
            break;
          case NBD_INFO_DESCRIPTION:
            {
              const char *desc = backend_export_description (conn->top_context);

              if (!desc) {
                debug ("newstyle negotiation: %s: "
                       "NBD_INFO_DESCRIPTION: no description to send",
                       optname);
                break;
              }
              if (send_newstyle_option_reply_info_str (option,
                                                       NBD_REP_INFO,
                                                       NBD_INFO_DESCRIPTION,
                                                       desc, -1) == -1)
                return -1;
            }
            break;
          default:
            debug ("newstyle negotiation: %s: "
                   "ignoring NBD_INFO_* request %u (%s)",
                   optname, (unsigned) info, name_of_nbd_info (info));
            break;
          }
        }
      }

      /* Unlike NBD_OPT_EXPORT_NAME, NBD_OPT_GO sends back an ACK
       * or ERROR packet.  If this was NBD_OPT_LIST, call .close.
       */
      if (send_newstyle_option_reply (option, NBD_REP_ACK) == -1)
        return -1;

      if (option == NBD_OPT_INFO) {
        if (backend_finalize (conn->top_context) == -1)
          return -1;
        backend_close (conn->top_context);
        conn->top_context = NULL;
      }

      break;

    case NBD_OPT_STRUCTURED_REPLY:
      if (optlen != 0) {
        if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID)
            == -1)
          return -1;
        if (conn_recv_full (data, optlen,
                            "read: %s: %m", name_of_nbd_opt (option)) == -1)
          return -1;
        continue;
      }

      debug ("newstyle negotiation: %s: client requested structured replies",
             name_of_nbd_opt (option));

      if (no_sr) {
        /* Must fail with ERR_UNSUP for qemu 4.2 to remain happy;
         * but failing with ERR_POLICY would have been nicer.
         */
        if (send_newstyle_option_reply (option, NBD_REP_ERR_UNSUP) == -1)
          return -1;
        debug ("newstyle negotiation: %s: structured replies are disabled",
               name_of_nbd_opt (option));
        break;
      }

      if (send_newstyle_option_reply (option, NBD_REP_ACK) == -1)
        return -1;

      conn->structured_replies = true;
      break;

    case NBD_OPT_LIST_META_CONTEXT:
    case NBD_OPT_SET_META_CONTEXT:
      {
        uint32_t opt_index;
        uint32_t exportnamelen;
        uint32_t nr_queries;
        uint32_t querylen;
        const char *what;

        if (conn_recv_full (data, optlen, "read: %s: %m", optname) == -1)
          return -1;

        /* Note that we support base:allocation whether or not the plugin
         * supports can_extents.
         */
        if (!conn->structured_replies) {
          if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID)
              == -1)
            return -1;
          continue;
        }

        /* Minimum length of the option payload is:
         *   32 bit export name length followed by empty export name
         * + 32 bit number of queries followed by no queries
         * = 8 bytes.
         */
        what = "optlen < 8";
        if (optlen < 8) {
        opt_meta_invalid_option_len:
          debug ("newstyle negotiation: %s: invalid option length: %s",
                 optname, what);

          if (send_newstyle_option_reply (option, NBD_REP_ERR_INVALID)
              == -1)
            return -1;
          continue;
        }

        memcpy (&exportnamelen, &data[0], 4);
        exportnamelen = be32toh (exportnamelen);
        what = "validating export name";
        if (check_export_name (option, &data[4], exportnamelen,
                               optlen - 8) == -1)
          goto opt_meta_invalid_option_len;

        /* Remember the export name: the NBD spec says that if the client
         * later uses NBD_OPT_GO on a different export, then the context
         * returned here is not usable.
         */
        if (option == NBD_OPT_SET_META_CONTEXT) {
          conn->exportname_from_set_meta_context =
            strndup (&data[4], exportnamelen);
          if (conn->exportname_from_set_meta_context == NULL) {
            nbdkit_error ("malloc: %m");
            return -1;
          }
        }

        opt_index = 4 + exportnamelen;

        /* Read the number of queries. */
        what = "reading number of queries";
        if (opt_index+4 > optlen)
          goto opt_meta_invalid_option_len;
        memcpy (&nr_queries, &data[opt_index], 4);
        nr_queries = be32toh (nr_queries);
        opt_index += 4;

        /* for LIST: nr_queries == 0 means return all meta contexts
         * for SET: nr_queries == 0 means reset all contexts
         */
        debug ("newstyle negotiation: %s: %s count: %d", optname,
               option == NBD_OPT_LIST_META_CONTEXT ? "query" : "set",
               nr_queries);
        if (option == NBD_OPT_SET_META_CONTEXT)
          conn->meta_context_base_allocation = false;
        if (nr_queries == 0) {
          if (option == NBD_OPT_LIST_META_CONTEXT) {
            if (send_newstyle_option_reply_meta_context (option,
                                                         NBD_REP_META_CONTEXT,
                                                         0, "base:allocation")
                == -1)
              return -1;
          }

          if (send_newstyle_option_reply (option, NBD_REP_ACK) == -1)
            return -1;
        }
        else {
          /* Read and answer each query. */
          while (nr_queries > 0) {
            what = "reading query string length";
            if (opt_index+4 > optlen)
              goto opt_meta_invalid_option_len;
            memcpy (&querylen, &data[opt_index], 4);
            querylen = be32toh (querylen);
            opt_index += 4;
            what = "reading query string";
            if (check_string (option, &data[opt_index], querylen,
                              optlen - opt_index, "meta context query") == -1)
              goto opt_meta_invalid_option_len;

            debug ("newstyle negotiation: %s: %s %.*s",
                   optname,
                   option == NBD_OPT_LIST_META_CONTEXT ? "query" : "set",
                   (int) querylen, &data[opt_index]);

            /* For LIST, "base:" returns all supported contexts in the
             * base namespace.  We only support "base:allocation".
             */
            if (option == NBD_OPT_LIST_META_CONTEXT &&
                querylen == 5 &&
                strncmp (&data[opt_index], "base:", 5) == 0) {
              if (send_newstyle_option_reply_meta_context
                  (option, NBD_REP_META_CONTEXT,
                   0, "base:allocation") == -1)
                return -1;
            }
            /* "base:allocation" requested by name. */
            else if (querylen == 15 &&
                     strncmp (&data[opt_index], "base:allocation", 15) == 0) {
              if (send_newstyle_option_reply_meta_context
                  (option, NBD_REP_META_CONTEXT,
                   option == NBD_OPT_SET_META_CONTEXT
                   ? base_allocation_id : 0,
                   "base:allocation") == -1)
                return -1;
              if (option == NBD_OPT_SET_META_CONTEXT)
                conn->meta_context_base_allocation = true;
            }
            /* Every other query must be ignored. */

            opt_index += querylen;
            nr_queries--;
          }
          if (send_newstyle_option_reply (option, NBD_REP_ACK) == -1)
            return -1;
        }
        debug ("newstyle negotiation: %s: reply complete", optname);
      }
      break;

    default:
      /* Unknown option. */
      if (send_newstyle_option_reply (option, NBD_REP_ERR_UNSUP) == -1)
        return -1;
      if (conn_recv_full (data, optlen,
                          "reading unknown option data: conn->recv: %m") == -1)
        return -1;
    }

    /* Note, since it's not very clear from the protocol doc, that the
     * client must send NBD_OPT_EXPORT_NAME or NBD_OPT_GO last, and
     * that ends option negotiation.
     */
    if (option == NBD_OPT_EXPORT_NAME || option == NBD_OPT_GO)
      break;
  }

  if (nr_options == 0) {
    nbdkit_error ("client spent too much time negotiating without selecting "
                  "an export");
    return -1;
  }

  /* In --tls=require / FORCEDTLS mode, we must have upgraded to TLS
   * by the time we finish option negotiation.  If not, give up.
   */
  if (tls == 2 && !conn->using_tls) {
    nbdkit_error ("non-TLS client tried to connect in --tls=require mode");
    return -1;
  }

  return 0;
}

A flaw was found in nbdkit due to to improperly caching plaintext state across the STARTTLS encryption boundary. A MitM attacker could use this flaw to inject a plaintext NBD_OPT_STRUCTURED_REPLY before proxying everything else a client sends to the server, potentially leading the client to terminate the NBD session. The highest threat from this vulnerability is to system availability.


patch 8.2.3564: invalid memory access when scrolling without valid screen

Problem:    Invalid memory access when scrolling without a valid screen.
Solution:   Do not set VALID_BOTLINE in w_valid.

update_topline(void)
{
    long	line_count;
    int		halfheight;
    int		n;
    linenr_T	old_topline;
#ifdef FEAT_DIFF
    int		old_topfill;
#endif
#ifdef FEAT_FOLDING
    linenr_T	lnum;
#endif
    int		check_topline = FALSE;
    int		check_botline = FALSE;
    long        *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
    int		save_so = *so_ptr;

    // If there is no valid screen and when the window height is zero just use
    // the cursor line.
    if (!screen_valid(TRUE) || curwin->w_height == 0)
    {
	check_cursor_lnum();
	curwin->w_topline = curwin->w_cursor.lnum;
	curwin->w_botline = curwin->w_topline;
	curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
	curwin->w_scbind_pos = 1;
	return;
    }

    check_cursor_moved(curwin);
    if (curwin->w_valid & VALID_TOPLINE)
	return;

    // When dragging with the mouse, don't scroll that quickly
    if (mouse_dragging > 0)
	*so_ptr = mouse_dragging - 1;

    old_topline = curwin->w_topline;
#ifdef FEAT_DIFF
    old_topfill = curwin->w_topfill;
#endif

    /*
     * If the buffer is empty, always set topline to 1.
     */
    if (BUFEMPTY())		// special case - file is empty
    {
	if (curwin->w_topline != 1)
	    redraw_later(NOT_VALID);
	curwin->w_topline = 1;
	curwin->w_botline = 2;
	curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
	curwin->w_scbind_pos = 1;
    }

    /*
     * If the cursor is above or near the top of the window, scroll the window
     * to show the line the cursor is in, with 'scrolloff' context.
     */
    else
    {
	if (curwin->w_topline > 1)
	{
	    // If the cursor is above topline, scrolling is always needed.
	    // If the cursor is far below topline and there is no folding,
	    // scrolling down is never needed.
	    if (curwin->w_cursor.lnum < curwin->w_topline)
		check_topline = TRUE;
	    else if (check_top_offset())
		check_topline = TRUE;
	}
#ifdef FEAT_DIFF
	    // Check if there are more filler lines than allowed.
	if (!check_topline && curwin->w_topfill > diff_check_fill(curwin,
							   curwin->w_topline))
	    check_topline = TRUE;
#endif

	if (check_topline)
	{
	    halfheight = curwin->w_height / 2 - 1;
	    if (halfheight < 2)
		halfheight = 2;

#ifdef FEAT_FOLDING
	    if (hasAnyFolding(curwin))
	    {
		// Count the number of logical lines between the cursor and
		// topline + scrolloff (approximation of how much will be
		// scrolled).
		n = 0;
		for (lnum = curwin->w_cursor.lnum;
				    lnum < curwin->w_topline + *so_ptr; ++lnum)
		{
		    ++n;
		    // stop at end of file or when we know we are far off
		    if (lnum >= curbuf->b_ml.ml_line_count || n >= halfheight)
			break;
		    (void)hasFolding(lnum, NULL, &lnum);
		}
	    }
	    else
#endif
		n = curwin->w_topline + *so_ptr - curwin->w_cursor.lnum;

	    // If we weren't very close to begin with, we scroll to put the
	    // cursor in the middle of the window.  Otherwise put the cursor
	    // near the top of the window.
	    if (n >= halfheight)
		scroll_cursor_halfway(FALSE);
	    else
	    {
		scroll_cursor_top(scrolljump_value(), FALSE);
		check_botline = TRUE;
	    }
	}

	else
	{
#ifdef FEAT_FOLDING
	    // Make sure topline is the first line of a fold.
	    (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
#endif
	    check_botline = TRUE;
	}
    }

    /*
     * If the cursor is below the bottom of the window, scroll the window
     * to put the cursor on the window.
     * When w_botline is invalid, recompute it first, to avoid a redraw later.
     * If w_botline was approximated, we might need a redraw later in a few
     * cases, but we don't want to spend (a lot of) time recomputing w_botline
     * for every small change.
     */
    if (check_botline)
    {
	if (!(curwin->w_valid & VALID_BOTLINE_AP))
	    validate_botline();

	if (curwin->w_botline <= curbuf->b_ml.ml_line_count)
	{
	    if (curwin->w_cursor.lnum < curwin->w_botline)
	    {
	      if (((long)curwin->w_cursor.lnum
					     >= (long)curwin->w_botline - *so_ptr
#ifdef FEAT_FOLDING
			|| hasAnyFolding(curwin)
#endif
			))
	      {
		lineoff_T	loff;

		// Cursor is (a few lines) above botline, check if there are
		// 'scrolloff' window lines below the cursor.  If not, need to
		// scroll.
		n = curwin->w_empty_rows;
		loff.lnum = curwin->w_cursor.lnum;
#ifdef FEAT_FOLDING
		// In a fold go to its last line.
		(void)hasFolding(loff.lnum, NULL, &loff.lnum);
#endif
#ifdef FEAT_DIFF
		loff.fill = 0;
		n += curwin->w_filler_rows;
#endif
		loff.height = 0;
		while (loff.lnum < curwin->w_botline
#ifdef FEAT_DIFF
			&& (loff.lnum + 1 < curwin->w_botline || loff.fill == 0)
#endif
			)
		{
		    n += loff.height;
		    if (n >= *so_ptr)
			break;
		    botline_forw(&loff);
		}
		if (n >= *so_ptr)
		    // sufficient context, no need to scroll
		    check_botline = FALSE;
	      }
	      else
		  // sufficient context, no need to scroll
		  check_botline = FALSE;
	    }
	    if (check_botline)
	    {
#ifdef FEAT_FOLDING
		if (hasAnyFolding(curwin))
		{
		    // Count the number of logical lines between the cursor and
		    // botline - scrolloff (approximation of how much will be
		    // scrolled).
		    line_count = 0;
		    for (lnum = curwin->w_cursor.lnum;
				   lnum >= curwin->w_botline - *so_ptr; --lnum)
		    {
			++line_count;
			// stop at end of file or when we know we are far off
			if (lnum <= 0 || line_count > curwin->w_height + 1)
			    break;
			(void)hasFolding(lnum, &lnum, NULL);
		    }
		}
		else
#endif
		    line_count = curwin->w_cursor.lnum - curwin->w_botline
								   + 1 + *so_ptr;
		if (line_count <= curwin->w_height + 1)
		    scroll_cursor_bot(scrolljump_value(), FALSE);
		else
		    scroll_cursor_halfway(FALSE);
	    }
	}
    }
    curwin->w_valid |= VALID_TOPLINE;

    /*
     * Need to redraw when topline changed.
     */
    if (curwin->w_topline != old_topline
#ifdef FEAT_DIFF
	    || curwin->w_topfill != old_topfill
#endif
	    )
    {
	dollar_vcol = -1;
	if (curwin->w_skipcol != 0)
	{
	    curwin->w_skipcol = 0;
	    redraw_later(NOT_VALID);
	}
	else
	    redraw_later(VALID);
	// May need to set w_skipcol when cursor in w_topline.
	if (curwin->w_cursor.lnum == curwin->w_topline)
	    validate_cursor();
    }

    *so_ptr = save_so;
}

vim is vulnerable to Heap-based Buffer Overflow


patch 8.2.4418: crash when using special multi-byte character

Problem:    Crash when using special multi-byte character.
Solution:   Don't use isalpha() for an arbitrary character.

unix_expandpath(
    garray_T	*gap,
    char_u	*path,
    int		wildoff,
    int		flags,		// EW_* flags
    int		didstar)	// expanded "**" once already
{
    char_u	*buf;
    char_u	*path_end;
    char_u	*p, *s, *e;
    int		start_len = gap->ga_len;
    char_u	*pat;
    regmatch_T	regmatch;
    int		starts_with_dot;
    int		matches;
    int		len;
    int		starstar = FALSE;
    static int	stardepth = 0;	    // depth for "**" expansion

    DIR		*dirp;
    struct dirent *dp;

    // Expanding "**" may take a long time, check for CTRL-C.
    if (stardepth > 0)
    {
	ui_breakcheck();
	if (got_int)
	    return 0;
    }

    // make room for file name
    buf = alloc(STRLEN(path) + BASENAMELEN + 5);
    if (buf == NULL)
	return 0;

    /*
     * Find the first part in the path name that contains a wildcard.
     * When EW_ICASE is set every letter is considered to be a wildcard.
     * Copy it into "buf", including the preceding characters.
     */
    p = buf;
    s = buf;
    e = NULL;
    path_end = path;
    while (*path_end != NUL)
    {
	// May ignore a wildcard that has a backslash before it; it will
	// be removed by rem_backslash() or file_pat_to_reg_pat() below.
	if (path_end >= path + wildoff && rem_backslash(path_end))
	    *p++ = *path_end++;
	else if (*path_end == '/')
	{
	    if (e != NULL)
		break;
	    s = p + 1;
	}
	else if (path_end >= path + wildoff
			 && (vim_strchr((char_u *)"*?[{~$", *path_end) != NULL
			     || (!p_fic && (flags & EW_ICASE)
					     && isalpha(PTR2CHAR(path_end)))))
	    e = p;
	if (has_mbyte)
	{
	    len = (*mb_ptr2len)(path_end);
	    STRNCPY(p, path_end, len);
	    p += len;
	    path_end += len;
	}
	else
	    *p++ = *path_end++;
    }
    e = p;
    *e = NUL;

    // Now we have one wildcard component between "s" and "e".
    // Remove backslashes between "wildoff" and the start of the wildcard
    // component.
    for (p = buf + wildoff; p < s; ++p)
	if (rem_backslash(p))
	{
	    STRMOVE(p, p + 1);
	    --e;
	    --s;
	}

    // Check for "**" between "s" and "e".
    for (p = s; p < e; ++p)
	if (p[0] == '*' && p[1] == '*')
	    starstar = TRUE;

    // convert the file pattern to a regexp pattern
    starts_with_dot = *s == '.';
    pat = file_pat_to_reg_pat(s, e, NULL, FALSE);
    if (pat == NULL)
    {
	vim_free(buf);
	return 0;
    }

    // compile the regexp into a program
    if (flags & EW_ICASE)
	regmatch.rm_ic = TRUE;		// 'wildignorecase' set
    else
	regmatch.rm_ic = p_fic;	// ignore case when 'fileignorecase' is set
    if (flags & (EW_NOERROR | EW_NOTWILD))
	++emsg_silent;
    regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
    if (flags & (EW_NOERROR | EW_NOTWILD))
	--emsg_silent;
    vim_free(pat);

    if (regmatch.regprog == NULL && (flags & EW_NOTWILD) == 0)
    {
	vim_free(buf);
	return 0;
    }

    // If "**" is by itself, this is the first time we encounter it and more
    // is following then find matches without any directory.
    if (!didstar && stardepth < 100 && starstar && e - s == 2
							  && *path_end == '/')
    {
	STRCPY(s, path_end + 1);
	++stardepth;
	(void)unix_expandpath(gap, buf, (int)(s - buf), flags, TRUE);
	--stardepth;
    }

    // open the directory for scanning
    *s = NUL;
    dirp = opendir(*buf == NUL ? "." : (char *)buf);

    // Find all matching entries
    if (dirp != NULL)
    {
	for (;;)
	{
	    dp = readdir(dirp);
	    if (dp == NULL)
		break;
	    if ((dp->d_name[0] != '.' || starts_with_dot
			|| ((flags & EW_DODOT)
			    && dp->d_name[1] != NUL
			    && (dp->d_name[1] != '.' || dp->d_name[2] != NUL)))
		 && ((regmatch.regprog != NULL && vim_regexec(&regmatch,
					     (char_u *)dp->d_name, (colnr_T)0))
		   || ((flags & EW_NOTWILD)
		     && fnamencmp(path + (s - buf), dp->d_name, e - s) == 0)))
	    {
		STRCPY(s, dp->d_name);
		len = STRLEN(buf);

		if (starstar && stardepth < 100)
		{
		    // For "**" in the pattern first go deeper in the tree to
		    // find matches.
		    STRCPY(buf + len, "/**");
		    STRCPY(buf + len + 3, path_end);
		    ++stardepth;
		    (void)unix_expandpath(gap, buf, len + 1, flags, TRUE);
		    --stardepth;
		}

		STRCPY(buf + len, path_end);
		if (mch_has_exp_wildcard(path_end)) // handle more wildcards
		{
		    // need to expand another component of the path
		    // remove backslashes for the remaining components only
		    (void)unix_expandpath(gap, buf, len + 1, flags, FALSE);
		}
		else
		{
		    stat_T  sb;

		    // no more wildcards, check if there is a match
		    // remove backslashes for the remaining components only
		    if (*path_end != NUL)
			backslash_halve(buf + len + 1);
		    // add existing file or symbolic link
		    if ((flags & EW_ALLLINKS) ? mch_lstat((char *)buf, &sb) >= 0
						      : mch_getperm(buf) >= 0)
		    {
#ifdef MACOS_CONVERT
			size_t precomp_len = STRLEN(buf)+1;
			char_u *precomp_buf =
			    mac_precompose_path(buf, precomp_len, &precomp_len);

			if (precomp_buf)
			{
			    mch_memmove(buf, precomp_buf, precomp_len);
			    vim_free(precomp_buf);
			}
#endif
			addfile(gap, buf, flags);
		    }
		}
	    }
	}

	closedir(dirp);
    }

    vim_free(buf);
    vim_regfree(regmatch.regprog);

    matches = gap->ga_len - start_len;
    if (matches > 0)
	qsort(((char_u **)gap->ga_data) + start_len, matches,
						   sizeof(char_u *), pstrcmp);
    return matches;
}

Use of Out-of-range Pointer Offset in GitHub repository vim/vim prior to 8.2.4418.


ftp_genlist(ftpbuf_t *ftp, const char *cmd, const char *path TSRMLS_DC)
{
	php_stream	*tmpstream = NULL;
	databuf_t	*data = NULL;
	char		*ptr;
	int		ch, lastch;
	int		size, rcvd;
	int		lines;
	char		**ret = NULL;
	char		**entry;
	char		*text;

	if ((tmpstream = php_stream_fopen_tmpfile()) == NULL) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create temporary file.  Check permissions in temporary files directory.");
		return NULL;
	}

	if (!ftp_type(ftp, FTPTYPE_ASCII)) {
		goto bail;
	}

	if ((data = ftp_getdata(ftp TSRMLS_CC)) == NULL) {
		goto bail;
	}
	ftp->data = data;

	if (!ftp_putcmd(ftp, cmd, path)) {
		goto bail;
	}
	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125 && ftp->resp != 226)) {
		goto bail;
	}

	/* some servers don't open a ftp-data connection if the directory is empty */
	if (ftp->resp == 226) {
		ftp->data = data_close(ftp, data);
		php_stream_close(tmpstream);
		return ecalloc(1, sizeof(char*));
	}

	/* pull data buffer into tmpfile */
	if ((data = data_accept(data, ftp TSRMLS_CC)) == NULL) {
		goto bail;
	}
	size = 0;
	lines = 0;
	lastch = 0;
	while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
		if (rcvd == -1) {
			goto bail;
		}

		php_stream_write(tmpstream, data->buf, rcvd);

		size += rcvd;
		for (ptr = data->buf; rcvd; rcvd--, ptr++) {
			if (*ptr == '\n' && lastch == '\r') {
				lines++;
			} else {
				size++;
			}
			lastch = *ptr;
		}
	}

	ftp->data = data_close(ftp, data);

	php_stream_rewind(tmpstream);

	ret = safe_emalloc((lines + 1), sizeof(char*), size * sizeof(char*));

	entry = ret;
	text = (char*) (ret + lines + 1);
	*entry = text;
	lastch = 0;
	while ((ch = php_stream_getc(tmpstream)) != EOF) {
		if (ch == '\n' && lastch == '\r') {
			*(text - 1) = 0;
			*++entry = text;
		} else {
			*text++ = ch;
		}
		lastch = ch;
	}
	*entry = NULL;

	php_stream_close(tmpstream);

	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
		efree(ret);
		return NULL;
	}

	return ret;
bail:
	ftp->data = data_close(ftp, data);
	php_stream_close(tmpstream);
	if (ret)
		efree(ret);
	return NULL;
}

Integer overflow in the ftp_genlist function in ext/ftp/ftp.c in PHP before 5.4.41, 5.5.x before 5.5.25, and 5.6.x before 5.6.9 allows remote FTP servers to execute arbitrary code via a long reply to a LIST command, leading to a heap-based buffer overflow.


check vertexIndex which might be -1 for broken DXF

bool DL_Dxf::handleLWPolylineData(DL_CreationInterface* /*creationInterface*/) {
    // Allocate LWPolyline vertices (group code 90):
    if (groupCode==90) {
        maxVertices = toInt(groupValue);
        if (maxVertices>0) {
            if (vertices!=NULL) {
                delete[] vertices;
            }
            vertices = new double[4*maxVertices];
            for (int i=0; i<maxVertices; ++i) {
                vertices[i*4] = 0.0;
                vertices[i*4+1] = 0.0;
                vertices[i*4+2] = 0.0;
                vertices[i*4+3] = 0.0;
            }
        }
        vertexIndex=-1;
        return true;
    }

    // Process LWPolylines vertices (group codes 10/20/30/42):
    else if (groupCode==10 || groupCode==20 ||
             groupCode==30 || groupCode==42) {

        if (vertexIndex<maxVertices-1 && groupCode==10) {
            vertexIndex++;
        }

        if (groupCode<=30) {
            if (vertexIndex>=0 && vertexIndex<maxVertices) {
                vertices[4*vertexIndex + (groupCode/10-1)] = toReal(groupValue);
            }
        } else if (groupCode==42 && vertexIndex<maxVertices) {
            vertices[4*vertexIndex + 3] = toReal(groupValue);
        }
        return true;
    }
    return false;
}

A code execution vulnerability exists in the DL_Dxf::handleLWPolylineData functionality of Ribbonsoft dxflib 3.17.0. A specially-crafted .dxf file can lead to a heap buffer overflow. An attacker can provide a malicious file to trigger this vulnerability.