Commit message: Prevent an assertion with invalid Unicode sequences

Invalid Unicode sequences, such as 0x2665 0xfe0e 0xfe0f,
can trick the Emoji iter code into returning an empty
segment, which then triggers an assertion in the itemizer.

Prevent this by ensuring that we make progress.

This issue was reported by Jeffrey M.
CVE: CVE-2018-15120
CWE: CWE-119
Vulnerability description: libpango in Pango 1.40.8 through 1.42.3, as used in hexchat and other products, allows remote attackers to cause a denial of service (application crash) or possibly have unspecified other impact via crafted text with invalid Unicode sequences.

Project: pango
File: pango-emoji.c

_pango_emoji_iter_next (PangoEmojiIter *iter)
{
  PangoEmojiType current_emoji_type = PANGO_EMOJI_TYPE_INVALID;

  if (iter->end == iter->text_end)
    return FALSE;

  iter->start = iter->end;

  for (; iter->end < iter->text_end; iter->end = g_utf8_next_char (iter->end))
    {
      gunichar ch = g_utf8_get_char (iter->end);

    /* Except at the beginning, ZWJ just carries over the emoji or neutral
     * text type, VS15 & VS16 we just carry over as well, since we already
     * resolved those through lookahead. Also, don't downgrade to text
     * presentation for emoji that are part of a ZWJ sequence, example
     * U+1F441 U+200D U+1F5E8, eye (text presentation) + ZWJ + left speech
     * bubble, see below. */
    if ((!(ch == kZeroWidthJoinerCharacter && !iter->is_emoji) &&
	 ch != kVariationSelector15Character &&
	 ch != kVariationSelector16Character &&
	 ch != kCombiningEnclosingCircleBackslashCharacter &&
	 !_pango_Is_Regional_Indicator(ch) &&
	 !((ch == kLeftSpeechBubbleCharacter ||
	    ch == kRainbowCharacter ||
	    ch == kMaleSignCharacter ||
	    ch == kFemaleSignCharacter ||
	    ch == kStaffOfAesculapiusCharacter) &&
	   !iter->is_emoji)) ||
	current_emoji_type == PANGO_EMOJI_TYPE_INVALID) {
      current_emoji_type = _pango_get_emoji_type (ch);
    }

    if (g_utf8_next_char (iter->end) < iter->text_end) /* Optimize. */
    {
      gunichar peek_char = g_utf8_get_char (g_utf8_next_char (iter->end));

      /* Variation Selectors */
      if (current_emoji_type ==
	      PANGO_EMOJI_TYPE_EMOJI_EMOJI &&
	  peek_char == kVariationSelector15Character) {
	current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_TEXT;
      }

      if ((current_emoji_type ==
	       PANGO_EMOJI_TYPE_EMOJI_TEXT ||
	   _pango_Is_Emoji_Keycap_Base(ch)) &&
	  peek_char == kVariationSelector16Character) {
	current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_EMOJI;
      }

      /* Combining characters Keycap... */
      if (_pango_Is_Emoji_Keycap_Base(ch) &&
	  peek_char == kCombiningEnclosingKeycapCharacter) {
	current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_EMOJI;
      };

      /* Regional indicators */
      if (_pango_Is_Regional_Indicator(ch) &&
	  _pango_Is_Regional_Indicator(peek_char)) {
	current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_EMOJI;
      }

      /* Upgrade text presentation emoji to emoji presentation when followed by
       * ZWJ, Example U+1F441 U+200D U+1F5E8, eye + ZWJ + left speech bubble. */
      if ((ch == kEyeCharacter ||
	   ch == kWavingWhiteFlagCharacter) &&
	  peek_char == kZeroWidthJoinerCharacter) {
	current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_EMOJI;
      }
    }

    if (iter->is_emoji == (gboolean) 2)
      iter->is_emoji = !PANGO_EMOJI_TYPE_IS_EMOJI (current_emoji_type);
     if (iter->is_emoji == PANGO_EMOJI_TYPE_IS_EMOJI (current_emoji_type))
     {
       iter->is_emoji = !PANGO_EMOJI_TYPE_IS_EMOJI (current_emoji_type);
       return TRUE;
     }
   }

  iter->is_emoji = PANGO_EMOJI_TYPE_IS_EMOJI (current_emoji_type);

  return TRUE;
}