Commit message: None
CVE: CVE-2014-3564
CWE: CWE-119
Vulnerability description: Multiple heap-based buffer overflows in the status_handler function in (1) engine-gpgsm.c and (2) engine-uiserver.c in GPGME before 1.5.1 allow remote attackers to cause a denial of service (crash) and possibly execute arbitrary code via vectors related to "different line lengths in a specific order."

Project: gnupg

gpgsm_keylist_ext (void *engine, const char *pattern[], int secret_only,
		   int reserved, gpgme_keylist_mode_t mode)
{
  engine_gpgsm_t gpgsm = engine;
  char *line;
  gpgme_error_t err;
  /* Length is "LISTSECRETKEYS " + p + '\0'.  */
  int length = 15 + 1;
  char *linep;
  int any_pattern = 0;
  int list_mode = 0;

  if (reserved)
    return gpg_error (GPG_ERR_INV_VALUE);

  if (mode & GPGME_KEYLIST_MODE_LOCAL)
    list_mode |= 1;
  if (mode & GPGME_KEYLIST_MODE_EXTERN)
    list_mode |= 2;

  /* Always send list-mode option because RESET does not reset it.  */
  if (asprintf (&line, "OPTION list-mode=%d", (list_mode & 3)) < 0)
    return gpg_error_from_syserror ();
  err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, line, NULL, NULL);
  free (line);
  if (err)
    return err;

  /* Always send key validation because RESET does not reset it.  */
  /* Use the validation mode if required.  We don't check for an error
     yet because this is a pretty fresh gpgsm features. */
  gpgsm_assuan_simple_command (gpgsm->assuan_ctx,
                               (mode & GPGME_KEYLIST_MODE_VALIDATE)?
                               "OPTION with-validation=1":
                               "OPTION with-validation=0" ,
                               NULL, NULL);
  gpgsm_assuan_simple_command (gpgsm->assuan_ctx,
                               (mode & GPGME_KEYLIST_MODE_WITH_SECRET)?
                               "OPTION with-secret=1":
                               "OPTION with-secret=0" ,
                               NULL, NULL);


  if (pattern && *pattern)
    {
      const char **pat = pattern;

      while (*pat)
	{
	  const char *patlet = *pat;

	  while (*patlet)
	    {
	      length++;
	      if (*patlet == '%' || *patlet == ' ' || *patlet == '+')
		length += 2;
	      patlet++;
	    }
	  pat++;
	  length++;
	}
    }
  line = malloc (length);
  if (!line)
    return gpg_error_from_syserror ();
  if (secret_only)
    {
      strcpy (line, "LISTSECRETKEYS ");
      linep = &line[15];
    }
  else
    {
      strcpy (line, "LISTKEYS ");
      linep = &line[9];
    }

  if (pattern && *pattern)
    {
      while (*pattern)
	{
	  const char *patlet = *pattern;

	  while (*patlet)
	    {
	      switch (*patlet)
		{
		case '%':
		  *(linep++) = '%';
		  *(linep++) = '2';
		  *(linep++) = '5';
		  break;
		case ' ':
		  *(linep++) = '%';
		  *(linep++) = '2';
		  *(linep++) = '0';
		  break;
		case '+':
		  *(linep++) = '%';
		  *(linep++) = '2';
		  *(linep++) = 'B';
		  break;
		default:
		  *(linep++) = *patlet;
		  break;
		}
	      patlet++;
	    }
          any_pattern = 1;
          *linep++ = ' ';
	  pattern++;
	}
    }
  if (any_pattern)
    linep--;
  *linep = '\0';

  gpgsm_clear_fd (gpgsm, INPUT_FD);
  gpgsm_clear_fd (gpgsm, OUTPUT_FD);
  gpgsm_clear_fd (gpgsm, MESSAGE_FD);
  gpgsm->inline_data = NULL;

  err = start (gpgsm, line);
  free (line);
  return err;
}

Project: gnupg

gpgsm_set_fd (engine_gpgsm_t gpgsm, fd_type_t fd_type, const char *opt)
{
  gpg_error_t err = 0;
  char line[COMMANDLINELEN];
  char *which;
  iocb_data_t *iocb_data;
#if USE_DESCRIPTOR_PASSING
  int dir;
#endif

  switch (fd_type)
    {
    case INPUT_FD:
      which = "INPUT";
      iocb_data = &gpgsm->input_cb;
      break;

    case OUTPUT_FD:
      which = "OUTPUT";
      iocb_data = &gpgsm->output_cb;
      break;

    case MESSAGE_FD:
      which = "MESSAGE";
      iocb_data = &gpgsm->message_cb;
      break;

    default:
      return gpg_error (GPG_ERR_INV_VALUE);
    }

#if USE_DESCRIPTOR_PASSING
  dir = iocb_data->dir;
  /* We try to short-cut the communication by giving GPGSM direct
     access to the file descriptor, rather than using a pipe.  */
  iocb_data->server_fd = _gpgme_data_get_fd (iocb_data->data);
  if (iocb_data->server_fd < 0)
    {
      int fds[2];

      if (_gpgme_io_pipe (fds, dir) < 0)
	return gpg_error_from_syserror ();

      iocb_data->fd = dir ? fds[0] : fds[1];
      iocb_data->server_fd = dir ? fds[1] : fds[0];

      if (_gpgme_io_set_close_notify (iocb_data->fd,
				      close_notify_handler, gpgsm))
	{
	  err = gpg_error (GPG_ERR_GENERAL);
	  goto leave_set_fd;
	}
    }

  err = assuan_sendfd (gpgsm->assuan_ctx, iocb_data->server_fd);
  if (err)
    goto leave_set_fd;

  _gpgme_io_close (iocb_data->server_fd);
  iocb_data->server_fd = -1;

  if (opt)
    snprintf (line, COMMANDLINELEN, "%s FD %s", which, opt);
  else
    snprintf (line, COMMANDLINELEN, "%s FD", which);
#else
  if (opt)
    snprintf (line, COMMANDLINELEN, "%s FD=%s %s",
              which, iocb_data->server_fd_str, opt);
  else
    snprintf (line, COMMANDLINELEN, "%s FD=%s",
              which, iocb_data->server_fd_str);
#endif

  err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, line, NULL, NULL);

#if USE_DESCRIPTOR_PASSING
 leave_set_fd:
  if (err)
    {
      _gpgme_io_close (iocb_data->fd);
      iocb_data->fd = -1;
      if (iocb_data->server_fd != -1)
        {
          _gpgme_io_close (iocb_data->server_fd);
          iocb_data->server_fd = -1;
        }
    }
#endif

  return err;
}

Project: gnupg

gpgsm_set_status_handler (void *engine, engine_status_handler_t fnc,
			  void *fnc_value)
{
  engine_gpgsm_t gpgsm = engine;

  gpgsm->status.fnc = fnc;
  gpgsm->status.fnc_value = fnc_value;
}

Project: gnupg

uiserver_assuan_simple_command (assuan_context_t ctx, char *cmd,
			     engine_status_handler_t status_fnc,
			     void *status_fnc_value)
{
  gpg_error_t err;
  char *line;
  size_t linelen;

  err = assuan_write_line (ctx, cmd);
  if (err)
    return err;

  do
    {
      err = assuan_read_line (ctx, &line, &linelen);
      if (err)
	return err;

      if (*line == '#' || !linelen)
	continue;

      if (linelen >= 2
	  && line[0] == 'O' && line[1] == 'K'
	  && (line[2] == '\0' || line[2] == ' '))
	return 0;
      else if (linelen >= 4
	  && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
	  && line[3] == ' ')
	err = atoi (&line[4]);
      else if (linelen >= 2
	       && line[0] == 'S' && line[1] == ' ')
	{
	  char *rest;
	  gpgme_status_code_t r;

	  rest = strchr (line + 2, ' ');
	  if (!rest)
	    rest = line + linelen; /* set to an empty string */
	  else
	    *(rest++) = 0;

	  r = _gpgme_parse_status (line + 2);

	  if (r >= 0 && status_fnc)
	    err = status_fnc (status_fnc_value, r, rest);
	  else
	    err = gpg_error (GPG_ERR_GENERAL);
	}
      else
	err = gpg_error (GPG_ERR_GENERAL);
    }
  while (!err);

  return err;
}

Project: gnupg

uiserver_cancel (void *engine)
{
  engine_uiserver_t uiserver = engine;

  if (!uiserver)
    return gpg_error (GPG_ERR_INV_VALUE);

  if (uiserver->status_cb.fd != -1)
    _gpgme_io_close (uiserver->status_cb.fd);
  if (uiserver->input_cb.fd != -1)
    _gpgme_io_close (uiserver->input_cb.fd);
  if (uiserver->output_cb.fd != -1)
    _gpgme_io_close (uiserver->output_cb.fd);
  if (uiserver->message_cb.fd != -1)
    _gpgme_io_close (uiserver->message_cb.fd);

  if (uiserver->assuan_ctx)
    {
      assuan_release (uiserver->assuan_ctx);
      uiserver->assuan_ctx = NULL;
    }

  return 0;
}

Project: gnupg

uiserver_set_fd (engine_uiserver_t uiserver, fd_type_t fd_type, const char *opt)
{
  gpg_error_t err = 0;
  char line[COMMANDLINELEN];
  char *which;
  iocb_data_t *iocb_data;
  int dir;

  switch (fd_type)
    {
    case INPUT_FD:
      which = "INPUT";
      iocb_data = &uiserver->input_cb;
      break;

    case OUTPUT_FD:
      which = "OUTPUT";
      iocb_data = &uiserver->output_cb;
      break;

    case MESSAGE_FD:
      which = "MESSAGE";
      iocb_data = &uiserver->message_cb;
      break;

    default:
      return gpg_error (GPG_ERR_INV_VALUE);
    }

  dir = iocb_data->dir;

  /* We try to short-cut the communication by giving UISERVER direct
     access to the file descriptor, rather than using a pipe.  */
  iocb_data->server_fd = _gpgme_data_get_fd (iocb_data->data);
  if (iocb_data->server_fd < 0)
    {
      int fds[2];

      if (_gpgme_io_pipe (fds, 0) < 0)
	return gpg_error_from_syserror ();

      iocb_data->fd = dir ? fds[0] : fds[1];
      iocb_data->server_fd = dir ? fds[1] : fds[0];

      if (_gpgme_io_set_close_notify (iocb_data->fd,
				      close_notify_handler, uiserver))
	{
	  err = gpg_error (GPG_ERR_GENERAL);
	  goto leave_set_fd;
	}
    }

  err = assuan_sendfd (uiserver->assuan_ctx, iocb_data->server_fd);
  if (err)
    goto leave_set_fd;

  _gpgme_io_close (iocb_data->server_fd);
  iocb_data->server_fd = -1;

  if (opt)
    snprintf (line, COMMANDLINELEN, "%s FD %s", which, opt);
  else
    snprintf (line, COMMANDLINELEN, "%s FD", which);

  err = uiserver_assuan_simple_command (uiserver->assuan_ctx, line, NULL, NULL);

 leave_set_fd:
  if (err)
    {
      _gpgme_io_close (iocb_data->fd);
      iocb_data->fd = -1;
      if (iocb_data->server_fd != -1)
        {
          _gpgme_io_close (iocb_data->server_fd);
          iocb_data->server_fd = -1;
        }
    }

  return err;
}

Project: gnupg

uiserver_sign (void *engine, gpgme_data_t in, gpgme_data_t out,
	       gpgme_sig_mode_t mode, int use_armor, int use_textmode,
	       int include_certs, gpgme_ctx_t ctx /* FIXME */)
{
  engine_uiserver_t uiserver = engine;
  gpgme_error_t err = 0;
  const char *protocol;
  char *cmd;
  gpgme_key_t key;

  if (!uiserver || !in || !out)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT)
    protocol = "";
  else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP)
    protocol = " --protocol=OpenPGP";
  else if (uiserver->protocol == GPGME_PROTOCOL_CMS)
    protocol = " --protocol=CMS";
  else
    return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL);

  if (asprintf (&cmd, "SIGN%s%s", protocol,
		(mode == GPGME_SIG_MODE_DETACH) ? " --detached" : "") < 0)
    return gpg_error_from_syserror ();

  key = gpgme_signers_enum (ctx, 0);
  if (key)
    {
      const char *s = NULL;

      if (key && key->uids)
        s = key->uids->email;

      if (s && strlen (s) < 80)
        {
          char buf[100];

          strcpy (stpcpy (buf, "SENDER --info "), s);
          err = uiserver_assuan_simple_command (uiserver->assuan_ctx, buf,
                                                uiserver->status.fnc,
                                                uiserver->status.fnc_value);
        }
      else
        err = gpg_error (GPG_ERR_INV_VALUE);
      gpgme_key_unref (key);
      if (err)
      {
	free (cmd);
	return err;
      }
  }

  uiserver->input_cb.data = in;
  err = uiserver_set_fd (uiserver, INPUT_FD,
			 map_data_enc (uiserver->input_cb.data));
  if (err)
    {
      free (cmd);
      return err;
    }
  uiserver->output_cb.data = out;
  err = uiserver_set_fd (uiserver, OUTPUT_FD, use_armor ? "--armor"
			 : map_data_enc (uiserver->output_cb.data));
  if (err)
    {
      free (cmd);
      return err;
    }
  uiserver->inline_data = NULL;

  err = start (uiserver, cmd);
  free (cmd);
  return err;
}