Skip to content

Protocol Security Violation: C7 Incomplete ICE connectivity check allows unvalidated candidate pair to trigger DTLS* #277

Description

@songxpu

1. Problem Overview

During the ICE checking phase, when libpeer receives a STUN Binding response, it only verifies message integrity. It does not correlate the transaction ID, nor does it check the symmetry between the response source address and the request destination address, before directly marking the current nominated_pair as successful. That pair is then written to selected_pair and the DTLS handshake is started. This allows DTLS to be established on a candidate pair that has not passed the full RFC 8445 ICE check.

2. Specification Requirements

The success of an ICE connectivity check cannot be determined by message integrity alone; the response must be correlated with the Binding request that produced it via the transaction ID, and the transport addresses in the request and response must be verified as symmetric. Only a candidate pair that passes this check can serve as the transport path for the subsequent DTLS. RFC 8842 requires that ICE checks complete before the DTLS handshake begins.

RFC 8445 §7.2.5 states:

"When a Binding response is received, it is correlated to the corresponding Binding request using the transaction ID [RFC5389], which then associates the response with the candidate pair for which the Binding request was sent."

RFC 8445 §7.2.5.2.1 states:

"The ICE agent MUST check that the source and destination transport addresses in the Binding request and response are symmetric."

RFC 8445 §7.2.5.3 states:

"A connectivity check is considered a success if each of the following criteria is true: The Binding request generated a success response; and The source and destination transport addresses in the Binding request and response are symmetric."

Therefore passing stun_msg_is_valid() cannot replace the transaction and address-symmetry checks. RFC 8842 §6 also requires that ICE connectivity checks be completed before the DTLS handshake starts.

3. Code Analysis

The receive path obtains the source address addr in agent_recv, but when calling agent_process_stun_response it only passes the parsed STUN message. That function verifies only integrity; it does not check the transaction ID or address symmetry before modifying the state of nominated_pair. agent_connectivity_check then sets that pair directly as selected_pair and proceeds to DTLS.

/* src/agent.c:372-394 — agent_recv */
int agent_recv(Agent* agent, uint8_t* buf, int len) {
  int ret = -1;
  StunMessage stun_msg;
  Address addr;
  if ((ret = agent_socket_recv(agent, &addr, buf, len)) > 0 && stun_probe(buf, len) == 0) {
    memcpy(stun_msg.buf, buf, ret);
    stun_msg.size = ret;
    stun_parse_msg_buf(&stun_msg);
    switch (stun_msg.stunclass) {
      case STUN_CLASS_RESPONSE:
        agent_process_stun_response(agent, &stun_msg);
        break;
      default:
        break;
    }
    ret = 0;
  }
  return ret;
}

/* src/agent.c:360-365 — agent_process_stun_response */
void agent_process_stun_response(Agent* agent, StunMessage* stun_msg) {
  switch (stun_msg->stunmethod) {
    case STUN_METHOD_BINDING:
      if (stun_msg_is_valid(stun_msg->buf, stun_msg->size, agent->remote_upwd) == 0) {
        agent->nominated_pair->state = ICE_CANDIDATE_STATE_SUCCEEDED;
      }
      break;
    default:
      break;
  }
}

/* src/agent.c:466-477 — agent_connectivity_check */
if (agent->nominated_pair->conncheck % AGENT_CONNCHECK_PERIOD == 0) {
  agent_create_binding_request(agent, &msg);
  agent_socket_send(agent, &agent->nominated_pair->remote->addr, msg.buf, msg.size);
}

agent_recv(agent, buf, sizeof(buf));

if (agent->nominated_pair->state == ICE_CANDIDATE_STATE_SUCCEEDED) {
  agent->selected_pair = agent->nominated_pair;
  return 0;
}

/* src/peer_connection.c:296-306 — peer_connection_loop */
case PEER_CONNECTION_CHECKING:
  if (agent_select_candidate_pair(&pc->agent) < 0) {
    STATE_CHANGED(pc, PEER_CONNECTION_FAILED);
  } else if (agent_connectivity_check(&pc->agent) == 0) {
    STATE_CHANGED(pc, PEER_CONNECTION_CONNECTED);
  }
  break;

case PEER_CONNECTION_CONNECTED:
  if (dtls_srtp_handshake(&pc->dtls_srtp, NULL) == 0) {

Call path:

agent_socket_recv (source address written to addr)
  -> agent_process_stun_response (addr/transaction not received; only nominated_pair modified)
  -> agent_connectivity_check (selected_pair = nominated_pair)
  -> peer_connection_loop (enters CONNECTED)
  -> dtls_srtp_handshake

4. Impact Analysis

This is both a functional bug and a security bug. An erroneous response can advance the wrong candidate pair, causing DTLS to start over an unvalidated ICE path and diverging from the standard ICE state machine. An attacker who can craft a response that passes integrity verification but has an unmatched source or transaction can influence the ICE state selection. Once the wrong pair is marked successful, the state machine proceeds directly to DTLS without preserving the failed state to retry other candidate pairs, which may cause connection failure or path confusion.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions