Commit message: Fix log-uninitialized-stack bug in rend_service_intro_established.

Fixes bug 23490; bugfix on 0.2.7.2-alpha.

TROVE-2017-008
CVE-2017-0380
CVE: CVE-2017-0380
CWE: CWE-532
Vulnerability description: The rend_service_intro_established function in or/rendservice.c in Tor before 0.2.8.15, 0.2.9.x before 0.2.9.12, 0.3.0.x before 0.3.0.11, 0.3.1.x before 0.3.1.7, and 0.3.2.x before 0.3.2.1-alpha, when SafeLogging is disabled, allows attackers to obtain sensitive information by leveraging access to the log files of a hidden service, because uninitialized stack data is included in an error message about construction of an introduction point circuit.

Project: tor
File: rendservice.c

poison_new_single_onion_hidden_service_dir_impl(const rend_service_t *service,
                                                const or_options_t* options)
{
  /* Passing a NULL service is a bug */
  if (BUG(!service)) {
    return -1;
  }

  /* We must only poison directories if we're in Single Onion mode */
  tor_assert(rend_service_non_anonymous_mode_enabled(options));

  int fd;
  int retval = -1;
  char *poison_fname = NULL;

  if (rend_service_is_ephemeral(service)) {
    log_info(LD_REND, "Ephemeral HS started in non-anonymous mode.");
    return 0;
  }

  /* Make sure we're only poisoning new hidden service directories */
  if (rend_service_private_key_exists(service)) {
    log_warn(LD_BUG, "Tried to single onion poison a service directory after "
             "the private key was created.");
    return -1;
  }

  /* Make sure the directory was created before calling this function. */
  if (BUG(rend_service_check_private_dir_impl(options, service, 0) < 0))
    return -1;

  poison_fname = rend_service_sos_poison_path(service);

  switch (file_status(poison_fname)) {
  case FN_DIR:
  case FN_ERROR:
    log_warn(LD_FS, "Can't read single onion poison file \"%s\"",
             poison_fname);
    goto done;
  case FN_FILE: /* single onion poison file already exists. NOP. */
  case FN_EMPTY: /* single onion poison file already exists. NOP. */
    log_debug(LD_FS, "Tried to re-poison a single onion poisoned file \"%s\"",
              poison_fname);
    break;
  case FN_NOENT:
    fd = tor_open_cloexec(poison_fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
    if (fd < 0) {
      log_warn(LD_FS, "Could not create single onion poison file %s",
               poison_fname);
      goto done;
    }
    close(fd);
    break;
  default:
    tor_assert(0);
  }

  retval = 0;

 done:
  tor_free(poison_fname);

  return retval;
}

Project: tor
File: rendservice.c

rend_get_service_list(const smartlist_t* substitute_service_list)
{
  /* It is safe to cast away the const here, because
   * rend_get_service_list_mutable does not actually modify the list */
  return rend_get_service_list_mutable((smartlist_t*)substitute_service_list);
}

Project: tor
File: rendservice.c

rend_get_service_list_mutable(smartlist_t* substitute_service_list)
{
  if (substitute_service_list) {
    return substitute_service_list;
  }

  /* If no special service list is provided, then just use the global one. */

  if (BUG(!rend_service_list)) {
    /* No global HS list, which is a programmer error. */
    return NULL;
  }

  return rend_service_list;
}

Project: tor
File: rendservice.c

rend_service_add_ephemeral(crypto_pk_t *pk,
                           smartlist_t *ports,
                           int max_streams_per_circuit,
                           int max_streams_close_circuit,
                           rend_auth_type_t auth_type,
                           smartlist_t *auth_clients,
                           char **service_id_out)
{
  *service_id_out = NULL;
  /* Allocate the service structure, and initialize the key, and key derived
   * parameters.
   */
  rend_service_t *s = tor_malloc_zero(sizeof(rend_service_t));
  s->directory = NULL; /* This indicates the service is ephemeral. */
  s->private_key = pk;
  s->auth_type = auth_type;
  s->clients = auth_clients;
  s->ports = ports;
  s->intro_period_started = time(NULL);
  s->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT;
  s->max_streams_per_circuit = max_streams_per_circuit;
  s->max_streams_close_circuit = max_streams_close_circuit;
  if (rend_service_derive_key_digests(s) < 0) {
    rend_service_free(s);
    return RSAE_BADPRIVKEY;
  }

  if (!s->ports || smartlist_len(s->ports) == 0) {
    log_warn(LD_CONFIG, "At least one VIRTPORT/TARGET must be specified.");
    rend_service_free(s);
    return RSAE_BADVIRTPORT;
  }
  if (s->auth_type != REND_NO_AUTH &&
      (!s->clients || smartlist_len(s->clients) == 0)) {
    log_warn(LD_CONFIG, "At least one authorized client must be specified.");
    rend_service_free(s);
    return RSAE_BADAUTH;
  }

  /* Enforcing pk/id uniqueness should be done by rend_service_load_keys(), but
   * it's not, see #14828.
   */
  if (rend_service_get_by_pk_digest(s->pk_digest)) {
    log_warn(LD_CONFIG, "Onion Service private key collides with an "
             "existing service.");
    rend_service_free(s);
    return RSAE_ADDREXISTS;
  }
  if (rend_service_get_by_service_id(s->service_id)) {
    log_warn(LD_CONFIG, "Onion Service id collides with an existing service.");
    rend_service_free(s);
    return RSAE_ADDREXISTS;
  }

  /* Initialize the service. */
  if (rend_add_service(NULL, s)) {
    return RSAE_INTERNAL;
  }
  *service_id_out = tor_strdup(s->service_id);

  log_debug(LD_CONFIG, "Added ephemeral Onion Service: %s", s->service_id);
  return RSAE_OKAY;
}

Project: tor
File: rendservice.c

rend_service_free_all(void)
{
  if (!rend_service_list)
    return;

  SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
                    rend_service_free(ptr));
  smartlist_free(rend_service_list);
  rend_service_list = NULL;
}

Project: tor
File: rendservice.c

rend_service_is_ephemeral(const struct rend_service_t *s)
{
  return (s->directory == NULL);
}

Project: tor
File: rendservice.c

rend_service_parse_intro_for_v2(
    rend_intro_cell_t *intro,
    const uint8_t *buf,
    size_t plaintext_len,
    char **err_msg_out)
{
  unsigned int klen;
  extend_info_t *extend_info = NULL;
  ssize_t ver_specific_len;

  /*
   * We accept version 3 too so that the v3 parser can call this with
   * an adjusted buffer for the latter part of a v3 cell, which is
   * identical to a v2 cell.
   */
  if (!(intro->version == 2 ||
        intro->version == 3)) {
    if (err_msg_out)
      tor_asprintf(err_msg_out,
                   "rend_service_parse_intro_for_v2() called with "
                   "bad version %d on INTRODUCE%d cell (this is a bug)",
                   intro->version,
                   (int)(intro->type));
    goto err;
  }

  /* 7 == version, IP and port, DIGEST_LEN == id, 2 == key length */
  if (plaintext_len < 7 + DIGEST_LEN + 2) {
    if (err_msg_out) {
      tor_asprintf(err_msg_out,
                   "truncated plaintext of encrypted parted of "
                   "version %d INTRODUCE%d cell",
                   intro->version,
                   (int)(intro->type));
    }

    goto err;
  }

  extend_info = tor_malloc_zero(sizeof(extend_info_t));
  tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf + 1));
  extend_info->port = ntohs(get_uint16(buf + 5));
  memcpy(extend_info->identity_digest, buf + 7, DIGEST_LEN);
  extend_info->nickname[0] = '$';
  base16_encode(extend_info->nickname + 1, sizeof(extend_info->nickname) - 1,
                extend_info->identity_digest, DIGEST_LEN);
  klen = ntohs(get_uint16(buf + 7 + DIGEST_LEN));

  /* 7 == version, IP and port, DIGEST_LEN == id, 2 == key length */
  if (plaintext_len < 7 + DIGEST_LEN + 2 + klen) {
    if (err_msg_out) {
      tor_asprintf(err_msg_out,
                   "truncated plaintext of encrypted parted of "
                   "version %d INTRODUCE%d cell",
                   intro->version,
                   (int)(intro->type));
    }

    goto err;
  }

  extend_info->onion_key =
    crypto_pk_asn1_decode((const char *)(buf + 7 + DIGEST_LEN + 2), klen);
  if (!extend_info->onion_key) {
    if (err_msg_out) {
      tor_asprintf(err_msg_out,
                   "error decoding onion key in version %d "
                   "INTRODUCE%d cell",
                   intro->version,
                   (intro->type));
    }

    goto err;
  }
  if (128 != crypto_pk_keysize(extend_info->onion_key)) {
    if (err_msg_out) {
      tor_asprintf(err_msg_out,
                   "invalid onion key size in version %d INTRODUCE%d cell",
                   intro->version,
                   (intro->type));
    }

    goto err;
  }

  ver_specific_len = 7+DIGEST_LEN+2+klen;

  if (intro->version == 2) intro->u.v2.extend_info = extend_info;
  else intro->u.v3.extend_info = extend_info;

  return ver_specific_len;

 err:
  extend_info_free(extend_info);

  return -1;
}

Project: tor
File: rendservice.c

rend_service_parse_intro_for_v3(
    rend_intro_cell_t *intro,
    const uint8_t *buf,
    size_t plaintext_len,
    char **err_msg_out)
{
  ssize_t adjust, v2_ver_specific_len, ts_offset;

  /* This should only be called on v3 cells */
  if (intro->version != 3) {
    if (err_msg_out)
      tor_asprintf(err_msg_out,
                   "rend_service_parse_intro_for_v3() called with "
                   "bad version %d on INTRODUCE%d cell (this is a bug)",
                   intro->version,
                   (int)(intro->type));
    goto err;
  }

  /*
   * Check that we have at least enough to get auth_len:
   *
   * 1 octet for version, 1 for auth_type, 2 for auth_len
   */
  if (plaintext_len < 4) {
    if (err_msg_out) {
      tor_asprintf(err_msg_out,
                   "truncated plaintext of encrypted parted of "
                   "version %d INTRODUCE%d cell",
                   intro->version,
                   (int)(intro->type));
    }

    goto err;
  }

  /*
   * The rend_client_send_introduction() function over in rendclient.c is
   * broken (i.e., fails to match the spec) in such a way that we can't
   * change it without breaking the protocol.  Specifically, it doesn't
   * emit auth_len when auth-type is REND_NO_AUTH, so everything is off
   * by two bytes after that.  Calculate ts_offset and do everything from
   * the timestamp on relative to that to handle this dain bramage.
   */

  intro->u.v3.auth_type = buf[1];
  if (intro->u.v3.auth_type != REND_NO_AUTH) {
    intro->u.v3.auth_len = ntohs(get_uint16(buf + 2));
    ts_offset = 4 + intro->u.v3.auth_len;
  } else {
    intro->u.v3.auth_len = 0;
    ts_offset = 2;
  }

  /* Check that auth len makes sense for this auth type */
  if (intro->u.v3.auth_type == REND_BASIC_AUTH ||
      intro->u.v3.auth_type == REND_STEALTH_AUTH) {
      if (intro->u.v3.auth_len != REND_DESC_COOKIE_LEN) {
        if (err_msg_out) {
          tor_asprintf(err_msg_out,
                       "wrong auth data size %d for INTRODUCE%d cell, "
                       "should be %d",
                       (int)(intro->u.v3.auth_len),
                       (int)(intro->type),
                       REND_DESC_COOKIE_LEN);
        }

        goto err;
      }
  }

  /* Check that we actually have everything up through the timestamp */
  if (plaintext_len < (size_t)(ts_offset)+4) {
    if (err_msg_out) {
      tor_asprintf(err_msg_out,
                   "truncated plaintext of encrypted parted of "
                   "version %d INTRODUCE%d cell",
                   intro->version,
                   (int)(intro->type));
    }

    goto err;
  }

  if (intro->u.v3.auth_type != REND_NO_AUTH &&
      intro->u.v3.auth_len > 0) {
    /* Okay, we can go ahead and copy auth_data */
    intro->u.v3.auth_data = tor_malloc(intro->u.v3.auth_len);
    /*
     * We know we had an auth_len field in this case, so 4 is
     * always right.
     */
    memcpy(intro->u.v3.auth_data, buf + 4, intro->u.v3.auth_len);
  }

  /*
   * From here on, the format is as in v2, so we call the v2 parser with
   * adjusted buffer and length.  We are 4 + ts_offset octets in, but the
   * v2 parser expects to skip over a version byte at the start, so we
   * adjust by 3 + ts_offset.
   */
  adjust = 3 + ts_offset;

  v2_ver_specific_len =
    rend_service_parse_intro_for_v2(intro,
                                    buf + adjust, plaintext_len - adjust,
                                    err_msg_out);

  /* Success in v2 parser */
  if (v2_ver_specific_len >= 0) return v2_ver_specific_len + adjust;
  /* Failure in v2 parser; it will have provided an err_msg */
  else return v2_ver_specific_len;

 err:
  return -1;
}

Project: tor
File: rendservice.c

rend_service_port_config_new(const char *socket_path)
{
  if (!socket_path)
    return tor_malloc_zero(sizeof(rend_service_port_config_t) + 1);

  const size_t pathlen = strlen(socket_path) + 1;
  rend_service_port_config_t *conf =
    tor_malloc_zero(sizeof(rend_service_port_config_t) + pathlen);
  memcpy(conf->unix_addr, socket_path, pathlen);
  conf->is_unix_addr = 1;
  return conf;
}

Project: tor
File: rendservice.c

rend_service_update_descriptor(rend_service_t *service)
{
  rend_service_descriptor_t *d;
  origin_circuit_t *circ;
  int i;

  rend_service_descriptor_free(service->desc);
  service->desc = NULL;

  d = service->desc = tor_malloc_zero(sizeof(rend_service_descriptor_t));
  d->pk = crypto_pk_dup_key(service->private_key);
  d->timestamp = time(NULL);
  d->timestamp -= d->timestamp % 3600; /* Round down to nearest hour */
  d->intro_nodes = smartlist_new();
  /* Support intro protocols 2 and 3. */
  d->protocols = (1 << 2) + (1 << 3);

  for (i = 0; i < smartlist_len(service->intro_nodes); ++i) {
    rend_intro_point_t *intro_svc = smartlist_get(service->intro_nodes, i);
    rend_intro_point_t *intro_desc;

    /* This intro point won't be listed in the descriptor... */
    intro_svc->listed_in_last_desc = 0;

    circ = find_intro_circuit(intro_svc, service->pk_digest);
    if (!circ || circ->base_.purpose != CIRCUIT_PURPOSE_S_INTRO) {
      /* This intro point's circuit isn't finished yet.  Don't list it. */
      continue;
    }

    /* ...unless this intro point is listed in the descriptor. */
    intro_svc->listed_in_last_desc = 1;

    /* We have an entirely established intro circuit.  Publish it in
     * our descriptor. */
    intro_desc = tor_malloc_zero(sizeof(rend_intro_point_t));
    intro_desc->extend_info = extend_info_dup(intro_svc->extend_info);
    if (intro_svc->intro_key)
      intro_desc->intro_key = crypto_pk_dup_key(intro_svc->intro_key);
    smartlist_add(d->intro_nodes, intro_desc);

    if (intro_svc->time_published == -1) {
      /* We are publishing this intro point in a descriptor for the
       * first time -- note the current time in the service's copy of
       * the intro point. */
      intro_svc->time_published = time(NULL);
    }
  }
}

Project: tor
File: rendservice.c

set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
{
  tor_assert(conn);
  tor_assert(p);
  tor_assert(p->is_unix_addr);

  conn->base_.socket_family = AF_UNIX;
  tor_addr_make_unspec(&conn->base_.addr);
  conn->base_.port = 1;
  conn->base_.address = tor_strdup(p->unix_addr);
  return 0;
}