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.
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_pairas successful. That pair is then written toselected_pairand 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:
RFC 8445 §7.2.5.2.1 states:
RFC 8445 §7.2.5.3 states:
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
addrinagent_recv, but when callingagent_process_stun_responseit 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 ofnominated_pair.agent_connectivity_checkthen sets that pair directly asselected_pairand proceeds to DTLS.Call path:
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.