Commit message: snmp plugin: Fix double free of request PDU

snmp_sess_synch_response() always frees request PDU, in both case of request
error and success. If error condition occurs inside of `while (status == 0)`
loop, double free of `req` happens.

Issue: #2291
Signed-off-by: Florian Forster <octo@collectd.org>
CVE: CVE-2017-16820
CWE: CWE-415
Vulnerability description: The csnmp_read_table function in snmp.c in the SNMP plugin in collectd before 5.6.3 is susceptible to a double free in a certain error case, which could lead to a crash (or potentially have other impact).

Project: collectd
File: snmp.c

static int csnmp_config_add_data_instance_prefix(data_definition_t *dd,
                                                 oconfig_item_t *ci) {
  int status;

  if (!dd->is_table) {
    WARNING("snmp plugin: data %s: InstancePrefix is ignored when `Table' "
            "is set to `false'.",
            dd->name);
    return (-1);
  }

  status = cf_util_get_string(ci, &dd->instance_prefix);
  return status;
} /* int csnmp_config_add_data_instance_prefix */

Project: collectd
File: snmp.c

static int csnmp_config_add_host(oconfig_item_t *ci) {
  host_definition_t *hd;
  int status = 0;

  /* Registration stuff. */
  char cb_name[DATA_MAX_NAME_LEN];

  hd = calloc(1, sizeof(*hd));
  if (hd == NULL)
    return (-1);
  hd->version = 2;
  C_COMPLAIN_INIT(&hd->complaint);

  status = cf_util_get_string(ci, &hd->name);
  if (status != 0) {
    sfree(hd);
    return status;
  }

  hd->sess_handle = NULL;
  hd->interval = 0;

  for (int i = 0; i < ci->children_num; i++) {
    oconfig_item_t *option = ci->children + i;
    status = 0;

    if (strcasecmp("Address", option->key) == 0)
      status = cf_util_get_string(option, &hd->address);
    else if (strcasecmp("Community", option->key) == 0)
      status = cf_util_get_string(option, &hd->community);
    else if (strcasecmp("Version", option->key) == 0)
      status = csnmp_config_add_host_version(hd, option);
    else if (strcasecmp("Collect", option->key) == 0)
      csnmp_config_add_host_collect(hd, option);
    else if (strcasecmp("Interval", option->key) == 0)
      cf_util_get_cdtime(option, &hd->interval);
    else if (strcasecmp("Username", option->key) == 0)
      status = cf_util_get_string(option, &hd->username);
    else if (strcasecmp("AuthProtocol", option->key) == 0)
      status = csnmp_config_add_host_auth_protocol(hd, option);
    else if (strcasecmp("PrivacyProtocol", option->key) == 0)
      status = csnmp_config_add_host_priv_protocol(hd, option);
    else if (strcasecmp("AuthPassphrase", option->key) == 0)
      status = cf_util_get_string(option, &hd->auth_passphrase);
    else if (strcasecmp("PrivacyPassphrase", option->key) == 0)
      status = cf_util_get_string(option, &hd->priv_passphrase);
    else if (strcasecmp("SecurityLevel", option->key) == 0)
      status = csnmp_config_add_host_security_level(hd, option);
    else if (strcasecmp("Context", option->key) == 0)
      status = cf_util_get_string(option, &hd->context);
    else {
      WARNING(
          "snmp plugin: csnmp_config_add_host: Option `%s' not allowed here.",
          option->key);
      status = -1;
    }

    if (status != 0)
      break;
  } /* for (ci->children) */

  while (status == 0) {
    if (hd->address == NULL) {
      WARNING("snmp plugin: `Address' not given for host `%s'", hd->name);
      status = -1;
      break;
    }
    if (hd->community == NULL && hd->version < 3) {
      WARNING("snmp plugin: `Community' not given for host `%s'", hd->name);
      status = -1;
      break;
    }
    if (hd->version == 3) {
      if (hd->username == NULL) {
        WARNING("snmp plugin: `Username' not given for host `%s'", hd->name);
        status = -1;
        break;
      }
      if (hd->security_level == 0) {
        WARNING("snmp plugin: `SecurityLevel' not given for host `%s'",
                hd->name);
        status = -1;
        break;
      }
      if (hd->security_level == SNMP_SEC_LEVEL_AUTHNOPRIV ||
          hd->security_level == SNMP_SEC_LEVEL_AUTHPRIV) {
        if (hd->auth_protocol == NULL) {
          WARNING("snmp plugin: `AuthProtocol' not given for host `%s'",
                  hd->name);
          status = -1;
          break;
        }
        if (hd->auth_passphrase == NULL) {
          WARNING("snmp plugin: `AuthPassphrase' not given for host `%s'",
                  hd->name);
          status = -1;
          break;
        }
      }
      if (hd->security_level == SNMP_SEC_LEVEL_AUTHPRIV) {
        if (hd->priv_protocol == NULL) {
          WARNING("snmp plugin: `PrivacyProtocol' not given for host `%s'",
                  hd->name);
          status = -1;
          break;
        }
        if (hd->priv_passphrase == NULL) {
          WARNING("snmp plugin: `PrivacyPassphrase' not given for host `%s'",
                  hd->name);
          status = -1;
          break;
        }
      }
    }

    break;
  } /* while (status == 0) */

  if (status != 0) {
    csnmp_host_definition_destroy(hd);
    return (-1);
  }

  DEBUG("snmp plugin: hd = { name = %s, address = %s, community = %s, version "
        "= %i }",
        hd->name, hd->address, hd->community, hd->version);

  ssnprintf(cb_name, sizeof(cb_name), "snmp-%s", hd->name);

  user_data_t ud = {.data = hd, .free_func = csnmp_host_definition_destroy};

  status =
      plugin_register_complex_read(/* group = */ NULL, cb_name, csnmp_read_host,
                                   hd->interval, /* user_data = */ &ud);
  if (status != 0) {
    ERROR("snmp plugin: Registering complex read function failed.");
    csnmp_host_definition_destroy(hd);
    return (-1);
  }

  return (0);
} /* int csnmp_config_add_host */

Project: collectd
File: snmp.c

static int csnmp_config_add_host_version(host_definition_t *hd,
                                         oconfig_item_t *ci) {
  int version;

  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
    WARNING("snmp plugin: The `Version' config option needs exactly one number "
            "argument.");
    return (-1);
  }

  version = (int)ci->values[0].value.number;
  if ((version < 1) || (version > 3)) {
    WARNING("snmp plugin: `Version' must either be `1', `2', or `3'.");
    return (-1);
  }

  hd->version = version;

  return (0);
} /* int csnmp_config_add_host_address */

Project: collectd
File: snmp.c

static void csnmp_host_definition_destroy(void *arg) /* {{{ */
{
  host_definition_t *hd;

  hd = arg;

  if (hd == NULL)
    return;

  if (hd->name != NULL) {
    DEBUG("snmp plugin: Destroying host definition for host `%s'.", hd->name);
  }

  csnmp_host_close_session(hd);

  sfree(hd->name);
  sfree(hd->address);
  sfree(hd->community);
  sfree(hd->username);
  sfree(hd->auth_passphrase);
  sfree(hd->priv_passphrase);
  sfree(hd->context);
  sfree(hd->data_list);

  sfree(hd);
} /* }}} void csnmp_host_definition_destroy */

Project: collectd
File: snmp.c

static int csnmp_strvbcopy_hexstring(char *dst, /* {{{ */
                                     const struct variable_list *vb,
                                     size_t dst_size) {
  char *buffer_ptr;
  size_t buffer_free;

  dst[0] = 0;

  buffer_ptr = dst;
  buffer_free = dst_size;

  for (size_t i = 0; i < vb->val_len; i++) {
    int status;

    status = snprintf(buffer_ptr, buffer_free, (i == 0) ? "%02x" : ":%02x",
                      (unsigned int)vb->val.bitstring[i]);
    assert(status >= 0);

    if (((size_t)status) >= buffer_free) /* truncated */
    {
      dst[dst_size - 1] = 0;
      return ENOMEM;
    } else /* if (status < buffer_free) */
    {
      buffer_ptr += (size_t)status;
      buffer_free -= (size_t)status;
    }
  }

  return 0;
} /* }}} int csnmp_strvbcopy_hexstring */