What went wrong

coturn is a TURN and STUN server: among other things, it relays traffic between clients and peers that cannot connect directly. Operators can restrict those destinations with denied-peer-ip and allowed-peer-ip rules, including ranges between two IPv6 addresses.

A range needs a total order. IPv6 addresses are sixteen-byte, most-significant-byte-first numbers, so comparison should stop at the first byte that differs. coturn’s addr_less_eq() instead examined every byte independently and returned false if any byte on the left was greater than the byte in the same position on the right.

That defines a component-wise partial order, not the numeric order of IPv6 addresses. The difference appears when a lower-order byte wraps while a higher-order byte increases. For example, the two-byte suffix 01 00 is below 01 ff, which is below 02 00. The old comparison nevertheless rejected 01 ff <= 02 00 because ff is greater than 00.

When coturn used that helper for both halves of a range test, a contiguous numeric interval became a smaller box of independently permitted byte values. An address could be inside the operator’s IPv6 deny range while the access-control check classified it as outside.

Why it mattered

An authenticated TURN client could ask the relay to reach an IPv6 peer that the operator had explicitly placed inside a denied range. If that peer was reachable from the coturn host, the relay could therefore become a network path to a service the rule was meant to fence off.

The published advisory rates the issue Moderate, represented as Medium in Argo, with a CVSS 4.0 score of 5.8. The direct result was bypass of the peer rule and TURN-mediated reachability. Any confidentiality, integrity or availability impact beyond that depended on the downstream service; the advisory does not claim compromise of coturn itself.

The prerequisites narrow the scope. The client needed valid TURN credentials, and the deployment needed a custom, non-prefix-aligned IPv6 min–max rule with a useful destination behind it. Exact-address rules and ordinary prefix-aligned controls were not affected in the same way. IPv4 used a separate comparison.

The final public record also corrects an earlier hypothesis: the confirmed result is under-matching of denied ranges. Independent analysis did not support the claim that the component-wise whitelist comparison could admit an address outside the true numeric interval.

How it was fixed

coturn replaced the sixteen independent comparisons with memcmp() over the network-order in6_addr. Because IPv6 addresses are stored most-significant byte first, that operation supplies the lexicographic total order the range check expected.

The fix added regression tests for native, non-prefix-aligned and wide IPv6 ranges, including boundaries that force a carry between bytes. It landed on 27 July 2026 and shipped in coturn 4.16.0, published on 31 July.

Operators unable to upgrade immediately can avoid arbitrary IPv6 min–max boundaries, use exact addresses or carefully validated prefix-aligned ranges, and enforce relay egress restrictions outside coturn. Upgrading remains the complete repair.

After the patch

A comparator is easy to mistake for plumbing, but any policy expressed as a range inherits its definition of order. A relation can look reasonable for equal prefixes and still fail the first time a lower-order component wraps.

The transferable review habit is to identify the canonical ordering for the whole type and use it everywhere. Tests should cross component boundaries rather than vary only the final byte: IP addresses, version tuples, timestamps and composite identifiers all hide similar mistakes. If the policy says “between”, the implementation first needs one unambiguous meaning of “before”.

Public record