Commit message: gmarkup: Fix crash in error handling path for closing elements

If something which looks like a closing tag is left unfinished, but
isn’t paired to an opening tag in the document, the error handling code
would do a null pointer dereference. Avoid that, at the cost of
introducing a new translatable error message.

Includes a test case, courtesy of pdknsk.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

https://gitlab.gnome.org/GNOME/glib/issues/1461
CVE: CVE-2018-16428
CWE: CWE-476
Vulnerability description: In GNOME GLib 2.56.1, g_markup_parse_context_end_parse() in gmarkup.c has a NULL pointer dereference.

Project: glib
File: gmarkup.c

g_markup_parse_context_end_parse (GMarkupParseContext  *context,
                                  GError              **error)
{
  g_return_val_if_fail (context != NULL, FALSE);
  g_return_val_if_fail (!context->parsing, FALSE);
  g_return_val_if_fail (context->state != STATE_ERROR, FALSE);

  if (context->partial_chunk != NULL)
    {
      g_string_free (context->partial_chunk, TRUE);
      context->partial_chunk = NULL;
    }

  if (context->document_empty)
    {
      set_error_literal (context, error, G_MARKUP_ERROR_EMPTY,
                         _("Document was empty or contained only whitespace"));
      return FALSE;
    }

  context->parsing = TRUE;

  switch (context->state)
    {
    case STATE_START:
      /* Nothing to do */
      break;

    case STATE_AFTER_OPEN_ANGLE:
      set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
                         _("Document ended unexpectedly just after an open angle bracket “<”"));
      break;

    case STATE_AFTER_CLOSE_ANGLE:
      if (context->tag_stack != NULL)
        {
          /* Error message the same as for INSIDE_TEXT */
          set_error (context, error, G_MARKUP_ERROR_PARSE,
                     _("Document ended unexpectedly with elements still open — "
                       "“%s” was the last element opened"),
                     current_element (context));
        }
      break;

    case STATE_AFTER_ELISION_SLASH:
      set_error (context, error, G_MARKUP_ERROR_PARSE,
                 _("Document ended unexpectedly, expected to see a close angle "
                   "bracket ending the tag <%s/>"), current_element (context));
      break;

    case STATE_INSIDE_OPEN_TAG_NAME:
      set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
                         _("Document ended unexpectedly inside an element name"));
      break;

    case STATE_INSIDE_ATTRIBUTE_NAME:
    case STATE_AFTER_ATTRIBUTE_NAME:
      set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
                         _("Document ended unexpectedly inside an attribute name"));
      break;

    case STATE_BETWEEN_ATTRIBUTES:
      set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
                         _("Document ended unexpectedly inside an element-opening "
                           "tag."));
      break;

    case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
      set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
                         _("Document ended unexpectedly after the equals sign "
                           "following an attribute name; no attribute value"));
      break;

    case STATE_INSIDE_ATTRIBUTE_VALUE_SQ:
    case STATE_INSIDE_ATTRIBUTE_VALUE_DQ:
      set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
                         _("Document ended unexpectedly while inside an attribute "
                           "value"));
      break;

    case STATE_INSIDE_TEXT:
      g_assert (context->tag_stack != NULL);
      set_error (context, error, G_MARKUP_ERROR_PARSE,
                 _("Document ended unexpectedly with elements still open — "
                   "“%s” was the last element opened"),
                 current_element (context));
      break;

    case STATE_AFTER_CLOSE_TAG_SLASH:
    case STATE_INSIDE_CLOSE_TAG_NAME:
    case STATE_AFTER_CLOSE_TAG_NAME:
      set_error (context, error, G_MARKUP_ERROR_PARSE,
                 _("Document ended unexpectedly inside the close tag for "
                   "element “%s”"), current_element (context));
      break;

    case STATE_INSIDE_PASSTHROUGH:
      set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
                         _("Document ended unexpectedly inside a comment or "
                           "processing instruction"));
      break;

    case STATE_ERROR:
    default:
      g_assert_not_reached ();
      break;
    }

  context->parsing = FALSE;

  return context->state != STATE_ERROR;
}