Impact
Erlang distribution over TLS run with the kernel 'check_ip' flag to enforce connecting nodes to be on the same LAN
are impacted by this CVE.
The check_ip/1 function in lib/ssl/src/inet_tls_dist.erl (line 723) calls inet:sockname(Socket) to obtain the peer IP address instead of the correct inet:peername(Socket). This means both IP and PeerIP are always identical (both are the local address), so the LAN subnet mask comparison always passes. The undocumented check_ip operational safeguard — intended to restrict distribution connections to the local subnet — is completely ineffective for TLS distribution.The check_ip/1 function in lib/ssl/src/inet_tls_dist.erl (line 723) calls inet:sockname(Socket) to obtain the peer IP address instead of the correct inet:peername(Socket). This means both IP and PeerIP are always identical (both are the local address), so the LAN subnet mask comparison always passes. The undocumented check_ip operational safeguard — intended to restrict distribution connections to the local subnet — is completely ineffective for TLS distribution.
3.2 The Bug
check_ip(Socket) ->
case application:get_env(check_ip) of
{ok, true} ->
maybe
{ok, {IP, _}} ?= inet:sockname(Socket), %% line 717: correct (local IP)
...
{ok, Netmask} ?= find_netmask(IP, Ifaddrs),
{ok, {PeerIP, _}} ?= inet:sockname(Socket), %% line 723: BUG! should be peername
...
mask(IP, Netmask) =:= mask(PeerIP, Netmask) %% always true (IP == PeerIP)
orelse {false, PeerIP}
...
end.
Since IP and PeerIP are both obtained from inet:sockname/1, they are always identical. The subnet mask comparison mask(IP, Netmask) =:= mask(PeerIP, Netmask) is trivially true for every connection.
3.3 Correct Implementation (kernel)
Both inet_tcp_dist.erl and inet_epmd_dist.erl correctly use inet:peername(Socket) to obtain the peer IP:
%% inet_epmd_dist.erl line 129
{ok, {PeerIp, _} = PeerAddress} ?= inet:peername(Socket),
check_ip(Ip, PeerIp),
```### 3.2 The Bug
```erlang
check_ip(Socket) ->
case application:get_env(check_ip) of
{ok, true} ->
maybe
{ok, {IP, _}} ?= inet:sockname(Socket), %% line 717: correct (local IP)
...
{ok, Netmask} ?= find_netmask(IP, Ifaddrs),
{ok, {PeerIP, _}} ?= inet:sockname(Socket), %% line 723: BUG! should be peername
...
mask(IP, Netmask) =:= mask(PeerIP, Netmask) %% always true (IP == PeerIP)
orelse {false, PeerIP}
...
end.
Since IP and PeerIP are both obtained from inet:sockname/1, they are always identical. The subnet mask comparison mask(IP, Netmask) =:= mask(PeerIP, Netmask) is trivially true for every connection.
3.3 Correct Implementation (kernel)
Both inet_tcp_dist.erl and inet_epmd_dist.erl correctly use inet:peername(Socket) to obtain the peer IP:
%% inet_epmd_dist.erl line 129
{ok, {PeerIp, _} = PeerAddress} ?= inet:peername(Socket),
check_ip(Ip, PeerIp),
3.4 Trigger Conditions
For exploitation, all of the following must hold:
- Erlang distribution is configured to use TLS (
-proto_dist inet_tls)
- Kernel env
check_ip is set to true
- The cluster's TLS verification accepts certificates not exclusively issued to cluster members — i.e., the CA is shared with other services (web servers, VPN, other teams). If the cluster uses a dedicated CA that only signs cluster node certificates, then certificate verification alone already restricts access and
check_ip adds no value even when working correctly.
- Attacker holds a valid certificate signed by that same CA (either legitimately — e.g., they run another service — or via compromise of a non-cluster certificate holder)
Workarounds
Implement ssl option verify_fun that performed the check correctly.
Affected/Unaffected Versions
A version larger than or equal to one of the listed patched versions is unaffected; otherwise, a version that satisfies an expression listed under affected versions is affected, and if it does not, it is unaffected.
The documentation of the OTP version scheme describes how versions should be compared. Note that versions used prior to OTP 17.0, when the new OTP version scheme was introduced, are never listed since it is not well defined how to compare those versions.
The vulnerability was introduced in OTP 26.0 (ssl 11.0). OTP versions before 26.0 does not implement this feature.
Impact
Erlang distribution over TLS run with the kernel 'check_ip' flag to enforce connecting nodes to be on the same LAN
are impacted by this CVE.
The
check_ip/1function inlib/ssl/src/inet_tls_dist.erl(line 723) callsinet:sockname(Socket)to obtain the peer IP address instead of the correctinet:peername(Socket). This means bothIPandPeerIPare always identical (both are the local address), so the LAN subnet mask comparison always passes. The undocumentedcheck_ipoperational safeguard — intended to restrict distribution connections to the local subnet — is completely ineffective for TLS distribution.Thecheck_ip/1function inlib/ssl/src/inet_tls_dist.erl(line 723) callsinet:sockname(Socket)to obtain the peer IP address instead of the correctinet:peername(Socket). This means bothIPandPeerIPare always identical (both are the local address), so the LAN subnet mask comparison always passes. The undocumentedcheck_ipoperational safeguard — intended to restrict distribution connections to the local subnet — is completely ineffective for TLS distribution.3.2 The Bug
Since
IPandPeerIPare both obtained frominet:sockname/1, they are always identical. The subnet mask comparisonmask(IP, Netmask) =:= mask(PeerIP, Netmask)is triviallytruefor every connection.3.3 Correct Implementation (kernel)
Both
inet_tcp_dist.erlandinet_epmd_dist.erlcorrectly useinet:peername(Socket)to obtain the peer IP:Since
IPandPeerIPare both obtained frominet:sockname/1, they are always identical. The subnet mask comparisonmask(IP, Netmask) =:= mask(PeerIP, Netmask)is triviallytruefor every connection.3.3 Correct Implementation (kernel)
Both
inet_tcp_dist.erlandinet_epmd_dist.erlcorrectly useinet:peername(Socket)to obtain the peer IP:3.4 Trigger Conditions
For exploitation, all of the following must hold:
-proto_dist inet_tls)check_ipis set totruecheck_ipadds no value even when working correctly.Workarounds
Implement ssl option
verify_funthat performed the check correctly.Affected/Unaffected Versions
A version larger than or equal to one of the listed patched versions is unaffected; otherwise, a version that satisfies an expression listed under affected versions is affected, and if it does not, it is unaffected.
The documentation of the OTP version scheme describes how versions should be compared. Note that versions used prior to OTP 17.0, when the new OTP version scheme was introduced, are never listed since it is not well defined how to compare those versions.
The vulnerability was introduced in OTP 26.0 (ssl 11.0). OTP versions before 26.0 does not implement this feature.