Commit message: Do not read beyond end of escaped string

Credit to OSS-Fuzz
CVE: CVE-2018-7051
CWE: CWE-125
Vulnerability description: An issue was discovered in Irssi before 1.0.7 and 1.1.x before 1.1.1. Certain nick names could result in out-of-bounds access when printing theme strings.

Project: irssi
File: themes.c

char *theme_format_expand_data(THEME_REC *theme, const char **format, theme_rm_col default_fg,
                               theme_rm_col default_bg, theme_rm_col *save_last_fg,
                               theme_rm_col *save_last_bg, int flags)
{
	return theme_format_expand_data_rec(theme, format, default_fg, default_bg, save_last_bg,
	                                    save_last_bg, flags, NULL);
}

Project: irssi
File: themes.c

char *theme_format_expand_get(THEME_REC *theme, const char **format)
{
	GString *str;
	char *ret;
	theme_rm_col dummy, reset;
	int braces = 1; /* we start with one brace opened */
	dummy.m[0] = '\0';
	strcpy(reset.m, "n");

	str = g_string_new(NULL);
	while (**format != '\0' && braces != 0) {
		if (**format == '{')
			braces++;
		else if (**format == '}')
			braces--;
		else if ((braces > 1) && (**format == ' ')) {
			g_string_append(str, "\\x20");
			(*format)++;
			continue;
		} else {
			theme_format_append_next(theme, str, format,
						 reset, reset,
						 &dummy, &dummy,
						 EXPAND_FLAG_IGNORE_REPLACES);
			continue;
		}

		if (braces == 0) {
			(*format)++;
			break;
		}

		g_string_append_c(str, **format);
		(*format)++;
	}

	ret = str->str;
        g_string_free(str, FALSE);
        return ret;
}

Project: irssi
File: themes.c

static char *theme_format_expand_data_rec(THEME_REC *theme, const char **format,
                                          theme_rm_col default_fg, theme_rm_col default_bg,
                                          theme_rm_col *save_last_fg, theme_rm_col *save_last_bg,
                                          int flags, GTree *block_list)
{
	GString *str;
	char *ret, *abstract;
	theme_rm_col last_fg, last_bg;
        int recurse_flags;

	last_fg = default_fg;
	last_bg = default_bg;
        recurse_flags = flags & EXPAND_FLAG_RECURSIVE_MASK;

	str = g_string_new(NULL);
	while (**format != '\0') {
		if ((flags & EXPAND_FLAG_ROOT) == 0 && **format == '}') {
			/* ignore } if we're expanding original string */
			(*format)++;
			break;
		}

		if (**format != '{') {
			if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) &&
			    **format == '$' && (*format)[1] == '0') {
				/* save the color before $0 ..
				   this is for the %n replacing */
				if (save_last_fg != NULL) {
					*save_last_fg = last_fg;
					save_last_fg = NULL;
				}
				if (save_last_bg != NULL) {
					*save_last_bg = last_bg;
					save_last_bg = NULL;
				}
			}

			theme_format_append_next(theme, str, format,
						 default_fg, default_bg,
						 &last_fg, &last_bg,
						 recurse_flags);
			continue;
		}

		(*format)++;
		if (**format == '\0' || **format == '}')
			break; /* error */

		/* get a single {...} */
		abstract = theme_format_expand_abstract(theme, format, &last_fg, &last_bg,
		                                        recurse_flags, block_list);
		if (abstract != NULL) {
			g_string_append(str, abstract);
			g_free(abstract);
		}
	}

		/* save the last color */
		if (save_last_fg != NULL)
			*save_last_fg = last_fg;
		if (save_last_bg != NULL)
			*save_last_bg = last_bg;

	ret = str->str;
        g_string_free(str, FALSE);
        return ret;
}

Project: irssi
File: themes.c

static char *theme_replace_expand(THEME_REC *theme, int index,
				  theme_rm_col default_fg, theme_rm_col default_bg,
				  theme_rm_col *last_fg, theme_rm_col *last_bg,
				  char chr, int flags)
{
	GSList *rec;
	char *ret, *abstract, data[2];

	rec = g_slist_nth(theme->replace_values, index);
	g_return_val_if_fail(rec != NULL, NULL);

	data[0] = chr; data[1] = '\0';

	abstract = rec->data;
	abstract = theme_format_expand_data(theme, (const char **) &abstract,
					    default_fg, default_bg,
					    last_fg, last_bg, (flags | EXPAND_FLAG_IGNORE_REPLACES));
	ret = parse_special_string(abstract, NULL, NULL, data, NULL,
				   PARSE_FLAG_ONLY_ARGS);
	g_free(abstract);
	return ret;
}

Project: irssi
File: themes.c

static char *theme_format_expand_abstract(THEME_REC *theme, const char **formatp,
                                          theme_rm_col *last_fg, theme_rm_col *last_bg, int flags,
                                          GTree *block_list)
{
	GString *str;
	const char *p, *format;
	char *abstract, *data, *ret;
	theme_rm_col default_fg, default_bg;
	int len;

	format = *formatp;
	default_fg = *last_fg;
	default_bg = *last_bg;

	/* get abstract name first */
	p = format;
	while (*p != '\0' && *p != ' ' &&
	       *p != '{' && *p != '}') p++;
	if (*p == '\0' || p == format)
		return NULL; /* error */

	len = (int) (p-format);
	abstract = g_strndup(format, len);

	/* skip the following space, if there's any more spaces they're
	   treated as arguments */
	if (*p == ' ') {
		len++;
		if ((flags & EXPAND_FLAG_IGNORE_EMPTY) && data_is_empty(&p)) {
			*formatp = p;
			g_free(abstract);
			return NULL;
		}
	}
	*formatp = format+len;

	if (block_list == NULL) {
		block_list = g_tree_new_full((GCompareDataFunc) g_strcmp0, NULL, g_free, NULL);
	} else {
		g_tree_ref(block_list);
	}

	/* get the abstract data */
	data = g_hash_table_lookup(theme->abstracts, abstract);
	if (data == NULL || g_tree_lookup(block_list, abstract) != NULL) {
		/* unknown abstract, just display the data */
		data = "$0-";
		g_free(abstract);
	} else {
		g_tree_insert(block_list, abstract, abstract);
	}
	abstract = g_strdup(data);

	/* we'll need to get the data part. it may contain
	   more abstracts, they are _NOT_ expanded. */
	data = theme_format_expand_get(theme, formatp);
	len = strlen(data);

	if (len > 1 && i_isdigit(data[len-1]) && data[len-2] == '$') {
		/* ends with $<digit> .. this breaks things if next
		   character is digit or '-' */
                char digit, *tmp;

		tmp = data;
		digit = tmp[len-1];
		tmp[len-1] = '\0';

		data = g_strdup_printf("%s{%c}", tmp, digit);
		g_free(tmp);
	}

	ret = parse_special_string(abstract, NULL, NULL, data, NULL,
				   PARSE_FLAG_ONLY_ARGS);
	g_free(abstract);
        g_free(data);
	str = g_string_new(NULL);
	p = ret;
	while (*p != '\0') {
		if (*p == '\\' && p[1] != '\0') {
			int chr;
			p++;
			chr = expand_escape(&p);
			g_string_append_c(str, chr != -1 ? chr : *p);
		} else
			g_string_append_c(str, *p);
		p++;
	}
	g_free(ret);
	abstract = str->str;
	g_string_free(str, FALSE);

	/* abstract may itself contain abstracts or replaces */
	p = abstract;
	ret = theme_format_expand_data_rec(theme, &p, default_fg, default_bg, last_fg, last_bg,
	                                   flags | EXPAND_FLAG_LASTCOLOR_ARG, block_list);
	g_free(abstract);
	g_tree_unref(block_list);
	return ret;
}