Commit message: TROVE-2017-004: Fix assertion failure in relay_send_end_cell_from_edge_

This fixes an assertion failure in relay_send_end_cell_from_edge_() when an
origin circuit and a cpath_layer = NULL were passed.

A service rendezvous circuit could do such a thing when a malformed BEGIN cell
is received but shouldn't in the first place because the service needs to send
an END cell on the circuit for which it can not do without a cpath_layer.

Fixes #22493

Reported-by: Roger Dingledine <arma@torproject.org>
Signed-off-by: David Goulet <dgoulet@torproject.org>
CVE: CVE-2017-0375
CWE: CWE-617
Vulnerability description: The hidden-service feature in Tor before 0.3.0.8 allows a denial of service (assertion failure and daemon exit) in the relay_send_end_cell_from_edge_ function via a malformed BEGIN cell.

Project: tor
File: connection_edge.c

begin_cell_parse(const cell_t *cell, begin_cell_t *bcell,
                 uint8_t *end_reason_out)
{
  relay_header_t rh;
  const uint8_t *body, *nul;

  memset(bcell, 0, sizeof(*bcell));
  *end_reason_out = END_STREAM_REASON_MISC;

  relay_header_unpack(&rh, cell->payload);
  if (rh.length > RELAY_PAYLOAD_SIZE) {
    return -2; /*XXXX why not TORPROTOCOL? */
  }

  bcell->stream_id = rh.stream_id;

  if (rh.command == RELAY_COMMAND_BEGIN_DIR) {
    bcell->is_begindir = 1;
    return 0;
  } else if (rh.command != RELAY_COMMAND_BEGIN) {
    log_warn(LD_BUG, "Got an unexpected command %d", (int)rh.command);
    *end_reason_out = END_STREAM_REASON_INTERNAL;
    return -1;
  }

  body = cell->payload + RELAY_HEADER_SIZE;
  nul = memchr(body, 0, rh.length);
  if (! nul) {
    log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
           "Relay begin cell has no \\0. Closing.");
    *end_reason_out = END_STREAM_REASON_TORPROTOCOL;
    return -1;
  }

  if (tor_addr_port_split(LOG_PROTOCOL_WARN,
                          (char*)(body),
                          &bcell->address,&bcell->port)<0) {
    log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
           "Unable to parse addr:port in relay begin cell. Closing.");
    *end_reason_out = END_STREAM_REASON_TORPROTOCOL;
    return -1;
  }
  if (bcell->port == 0) {
    log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
           "Missing port in relay begin cell. Closing.");
    tor_free(bcell->address);
    *end_reason_out = END_STREAM_REASON_TORPROTOCOL;
    return -1;
  }
  if (body + rh.length >= nul + 4)
    bcell->flags = ntohl(get_uint32(nul+1));

  return 0;
}

Project: tor
File: connection_edge.c

connection_ap_handshake_process_socks(entry_connection_t *conn)
{
  socks_request_t *socks;
  int sockshere;
  const or_options_t *options = get_options();
  int had_reply = 0;
  connection_t *base_conn = ENTRY_TO_CONN(conn);

  tor_assert(conn);
  tor_assert(base_conn->type == CONN_TYPE_AP);
  tor_assert(base_conn->state == AP_CONN_STATE_SOCKS_WAIT);
  tor_assert(conn->socks_request);
  socks = conn->socks_request;

  log_debug(LD_APP,"entered.");

  sockshere = fetch_from_buf_socks(base_conn->inbuf, socks,
                                   options->TestSocks, options->SafeSocks);

  if (socks->replylen) {
    had_reply = 1;
    connection_write_to_buf((const char*)socks->reply, socks->replylen,
                            base_conn);
    socks->replylen = 0;
    if (sockshere == -1) {
      /* An invalid request just got a reply, no additional
       * one is necessary. */
      socks->has_finished = 1;
    }
  }

  if (sockshere == 0) {
    log_debug(LD_APP,"socks handshake not all here yet.");
    return 0;
  } else if (sockshere == -1) {
    if (!had_reply) {
      log_warn(LD_APP,"Fetching socks handshake failed. Closing.");
      connection_ap_handshake_socks_reply(conn, NULL, 0,
                                          END_STREAM_REASON_SOCKSPROTOCOL);
    }
    connection_mark_unattached_ap(conn,
                              END_STREAM_REASON_SOCKSPROTOCOL |
                              END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
    return -1;
  } /* else socks handshake is done, continue processing */

  if (SOCKS_COMMAND_IS_CONNECT(socks->command))
    control_event_stream_status(conn, STREAM_EVENT_NEW, 0);
  else
    control_event_stream_status(conn, STREAM_EVENT_NEW_RESOLVE, 0);

  return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
}

Project: tor
File: connection_edge.c

connection_ap_handshake_socks_resolved_addr(entry_connection_t *conn,
                                            const tor_addr_t *answer,
                                            int ttl,
                                            time_t expires)
{
  if (tor_addr_family(answer) == AF_INET) {
    uint32_t a = tor_addr_to_ipv4n(answer); /* network order */
    connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_IPV4,4,
                                           (uint8_t*)&a,
                                           ttl, expires);
  } else if (tor_addr_family(answer) == AF_INET6) {
    const uint8_t *a = tor_addr_to_in6_addr8(answer);
    connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_IPV6,16,
                                           a,
                                           ttl, expires);
  } else {
    log_warn(LD_BUG, "Got called with address of unexpected family %d",
             tor_addr_family(answer));
    connection_ap_handshake_socks_resolved(conn,
                                           RESOLVED_TYPE_ERROR,0,NULL,-1,-1);
  }
}

Project: tor
File: connection_edge.c

connection_edge_end(edge_connection_t *conn, uint8_t reason)
{
  char payload[RELAY_PAYLOAD_SIZE];
  size_t payload_len=1;
  circuit_t *circ;
  uint8_t control_reason = reason;

  if (conn->edge_has_sent_end) {
    log_warn(LD_BUG,"(Harmless.) Calling connection_edge_end (reason %d) "
             "on an already ended stream?", reason);
    tor_fragile_assert();
    return -1;
  }

  if (conn->base_.marked_for_close) {
    log_warn(LD_BUG,
             "called on conn that's already marked for close at %s:%d.",
             conn->base_.marked_for_close_file, conn->base_.marked_for_close);
    return 0;
  }

  circ = circuit_get_by_edge_conn(conn);
  if (circ && CIRCUIT_PURPOSE_IS_CLIENT(circ->purpose)) {
    /* If this is a client circuit, don't send the server an informative
     * reason code; it doesn't need to know why the client stream is
     * failing. */
    reason = END_STREAM_REASON_MISC;
  }

  payload[0] = (char)reason;
  if (reason == END_STREAM_REASON_EXITPOLICY &&
      !connection_edge_is_rendezvous_stream(conn)) {
    int addrlen;
    if (tor_addr_family(&conn->base_.addr) == AF_INET) {
      set_uint32(payload+1, tor_addr_to_ipv4n(&conn->base_.addr));
      addrlen = 4;
    } else {
      memcpy(payload+1, tor_addr_to_in6_addr8(&conn->base_.addr), 16);
      addrlen = 16;
    }
    set_uint32(payload+1+addrlen, htonl(dns_clip_ttl(conn->address_ttl)));
    payload_len += 4+addrlen;
  }

  if (circ && !circ->marked_for_close) {
    log_debug(LD_EDGE,"Sending end on conn (fd "TOR_SOCKET_T_FORMAT").",
              conn->base_.s);
    connection_edge_send_command(conn, RELAY_COMMAND_END,
                                 payload, payload_len);
    /* We'll log warn if the connection was an hidden service and couldn't be
     * made because the service wasn't available. */
    warn_if_hs_unreachable(conn, control_reason);
  } else {
    log_debug(LD_EDGE,"No circ to send end on conn "
              "(fd "TOR_SOCKET_T_FORMAT").",
              conn->base_.s);
  }

  conn->edge_has_sent_end = 1;
  conn->end_reason = control_reason;
  return 0;
}

Project: tor
File: connection_edge.c

connection_edge_process_inbuf(edge_connection_t *conn, int package_partial)
{
  tor_assert(conn);

  switch (conn->base_.state) {
    case AP_CONN_STATE_SOCKS_WAIT:
      if (connection_ap_handshake_process_socks(EDGE_TO_ENTRY_CONN(conn)) <0) {
        /* already marked */
        return -1;
      }
      return 0;
    case AP_CONN_STATE_NATD_WAIT:
      if (connection_ap_process_natd(EDGE_TO_ENTRY_CONN(conn)) < 0) {
        /* already marked */
        return -1;
      }
      return 0;
    case AP_CONN_STATE_OPEN:
    case EXIT_CONN_STATE_OPEN:
      if (connection_edge_package_raw_inbuf(conn, package_partial, NULL) < 0) {
        /* (We already sent an end cell if possible) */
        connection_mark_for_close(TO_CONN(conn));
        return -1;
      }
      return 0;
    case AP_CONN_STATE_CONNECT_WAIT:
      if (connection_ap_supports_optimistic_data(EDGE_TO_ENTRY_CONN(conn))) {
        log_info(LD_EDGE,
                 "data from edge while in '%s' state. Sending it anyway. "
                 "package_partial=%d, buflen=%ld",
                 conn_state_to_string(conn->base_.type, conn->base_.state),
                 package_partial,
                 (long)connection_get_inbuf_len(TO_CONN(conn)));
        if (connection_edge_package_raw_inbuf(conn, package_partial, NULL)<0) {
          /* (We already sent an end cell if possible) */
          connection_mark_for_close(TO_CONN(conn));
          return -1;
        }
        return 0;
      }
      /* Fall through if the connection is on a circuit without optimistic
       * data support. */
    case EXIT_CONN_STATE_CONNECTING:
    case AP_CONN_STATE_RENDDESC_WAIT:
    case AP_CONN_STATE_CIRCUIT_WAIT:
    case AP_CONN_STATE_RESOLVE_WAIT:
    case AP_CONN_STATE_CONTROLLER_WAIT:
      log_info(LD_EDGE,
               "data from edge while in '%s' state. Leaving it on buffer.",
               conn_state_to_string(conn->base_.type, conn->base_.state));
      return 0;
  }
  log_warn(LD_BUG,"Got unexpected state %d. Closing.",conn->base_.state);
  tor_fragile_assert();
  connection_edge_end(conn, END_STREAM_REASON_INTERNAL);
  connection_mark_for_close(TO_CONN(conn));
  return -1;
}