Skip to content

Protocol Security Violation: C6 ICE candidate set reused after DTLS reset violates RFC 8842 3-tuple requirement #276

Description

@songxpu

1. Problem Overview

When libpeer generates an offer after a DTLS association has already been established, it resets DTLS and generates a new certificate and fingerprint, but continues to use the UDP socket address and port bound when the Agent was created to generate host candidates. This offerer does not allocate a fresh 3-tuple that has not been recently used, nor a new ICE candidate set, for the new association, thereby violating RFC 8842. This issue does not trigger during the initial connection establishment or when generating SDP without actually establishing a new association.

2. Specification Requirements

RFC 8842 Section 5.1 requires that when an offerer needs to establish a new DTLS association over an unordered transport such as UDP, it MUST allocate a new 3-tuple for the new offer so that packets associated with the new association can be distinguished from packets associated with any other DTLS association. RFC 8842 Section 6 further requires that when establishing a new DTLS association over an unordered transport, at least one endpoint MUST allocate a completely new set of ICE candidates not recently used for any other DTLS association. The offerer in this path still copies the candidate address from the same UDP socket, failing to meet this requirement.

RFC 8842 §5.1 "General" states:

"When an offerer needs to establish a new DTLS association, and if an unordered transport (e.g., UDP) is used, the offerer MUST allocate a new 3-tuple for the offer in such a way that the offerer can disambiguate any packets associated with the new DTLS association from any packets associated with any other DTLS association."

The official text of RFC 8842 §6 "ICE Considerations" reads:

"When a new DTLS association is established over an unordered transport, in order to disambiguate any packets associated with the newly established DTLS association, at least one of the endpoints MUST allocate a completely new set of ICE candidates that were not recently used for any other DTLS association."

3. Code Analysis

The SDP generation of a subsequent offer calls dtls_srtp_reset_session and dtls_srtp_init to start a new DTLS lifecycle, but the underlying ICE candidate socket still remains in the initially created Agent. agent_clear_candidates only clears the candidate counts without closing or reopening the UDP socket, and agent_create_host_addr copies the same bind_addr into a new array each time, so the host candidate of the new offer continues to carry the old port.

/* src/peer_connection.c:433-448 — peer_connection_create_sdp, subsequent offer */
case SDP_TYPE_OFFER:
  role = DTLS_SRTP_ROLE_SERVER;
  agent_clear_candidates(&pc->agent);
  pc->agent.mode = AGENT_MODE_CONTROLLING;
  break;
dtls_srtp_reset_session(&pc->dtls_srtp);
dtls_srtp_init(&pc->dtls_srtp, role, pc);

/* src/peer_connection.c:488-496 — peer_connection_create_sdp */
agent_gather_candidate(&pc->agent, NULL, NULL, NULL);
agent_get_local_description(&pc->agent, description, sizeof(pc->temp_buf));

/* src/agent.c:20-24 — agent_clear_candidates */
agent->local_candidates_count = 0;
agent->remote_candidates_count = 0;
agent->candidate_pairs_num = 0;

/* src/agent.c:26-32 — agent_create */
udp_socket_open(&agent->udp_sockets[0], AF_INET, 0);

/* src/agent.c:125-143 — agent_create_host_addr */
ice_candidate_create(ice_candidate, agent->local_candidates_count,
                     ICE_CANDIDATE_TYPE_HOST, &agent->udp_sockets[i].bind_addr);
if (ports_get_host_addr(&ice_candidate->addr, iface_prefx[j])) {
  agent->local_candidates_count++;
}

agent_clear_candidates() neither closes nor reopens the UDP socket, nor does it preserve a set of "recently used" candidates. Therefore, under the trigger condition, the candidate set of the new offer cannot provide the RFC-required basis for distinguishing the new DTLS association.

4. Impact Analysis

This is a functional bug. New and old DTLS datagrams share the same transport 3-tuple, so the remote side cannot reliably distinguish late-arriving packets of the old association from new handshake packets, which may cause renegotiation failure or repeated timeouts. It is also a security bug: the same 3-tuple cannot provide the association demultiplexing boundary designed by RFC 8842, and old-session packets may interfere with new-session state. Calling the same offer API again will still reuse the Agent's socket and port, so a mere retry does not produce a new candidate set.

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