CVE-2026-74958
Overview
Background
- WebRTC ICE
- the connectivity-establishment machinery that has the browser send UDP/TCP probes to candidate peer addresses to negotiate a media path.
- `NrSocket`
- Gecko’s
NSPR-backed socket wrapper used by mtransport for WebRTC traffic, which runs in the socket process or parent process rather than the content process. - `NS_CheckPortSafety`
- Necko’s generic guard that rejects outgoing connections to well-known service ports (the “bad ports” list) to prevent cross-protocol abuse.
- `IsForbiddenAddress`
- a static
NrSocketBasehelper that decides whether a destinationnr_transport_addrtargets a port WebRTC must not talk to.
Root Cause Analysis
The vulnerable code path was NrSocket::sendto and NrSocket::connect, which pushed data out via PR_SendTo/PR_Connect without ever consulting IsForbiddenAddress, so the port-safety invariant that outbound WebRTC traffic must avoid restricted service ports was never enforced on the actual egress path. Because these methods run in the socket/parent process on behalf of a content-supplied ICE candidate list, an attacker-influenced destination could steer packets to ports Necko otherwise blocks.
The fix inserts an IsForbiddenAddress(to)/IsForbiddenAddress(addr) check at the top of each method, silently dropping a forbidden sendto (reporting success so the caller does not retry) and failing connect with R_WOULDBLOCK. It also hardens IsForbiddenAddress itself: port 0 is short-circuited as allowed, a IceServerParser::kGoodWebrtcPortList allowlist is consulted before the generic block list, and the function now falls back to NS_CheckPortSafety, so legitimate ports like 53 still work while blocked ports are refused.
The fix works because it moves the enforcement to the single choke point through which every outbound datagram and connection passes.
IsForbiddenAddress into both NrSocket::sendto and NrSocket::connect so the guard actually runs before any packet leaves.Attack Path
- Craft candidates A malicious page drives a WebRTC session and supplies ICE candidates pointing at restricted local or internal destination ports.
- Reach the socket process
Candidate gathering causes
NrSocketin the socket/parent process to attemptconnectorsendtotoward those addresses. - Bypass the block list
Because neither method called
IsForbiddenAddress, theNS_CheckPortSafetyrestriction was skipped and packets were emitted to otherwise-forbidden ports. - Observe behavior Differences in connectivity results or timing let the attacker infer which internal services or ports respond, disclosing information about the host or network.
Impact Assessment
NrUdpSocketIpc/NrTcpSocket), acting on content-supplied candidate addresses. The precondition is that the victim engages an attacker-controlled page that can initiate a WebRTC connection and drive ICE candidate processing.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdom/media/webrtc/transport/nr_socket_prsock.cpp |
modified | |
fordom/media/webrtc/transport/nr_socket_prsock.cpp |
modified | |
TEST_Fdom/media/webrtc/transport/test/test_nr_socket_unittest.cpp |
modified |
Files Changed
dom/media/webrtc/transport/nr_socket_prsock.cppdom/media/webrtc/transport/nr_socket_prsock.hdom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
Audit Directions
- Guard-defined-but-never-calledSearch for safety helpers like
IsForbiddenAddressthat exist as static utilities but are not invoked on the actual I/O path they are meant to protect. - Egress choke pointsVerify that every
sendto/connect/bindimplementation across mtransport socket classes (NrUdpSocketIpc,NrTcpSocket, and proxied variants) enforces the same port-safety policy, not just one class. - Allowlist driftReview
IceServerParser::kGoodWebrtcPortListandNS_CheckPortSafetyinteractions to ensure newly allowed WebRTC ports do not silently re-open cross-protocol abuse vectors.
Patch
diff --git a/dom/media/webrtc/transport/nr_socket_prsock.cpp b/dom/media/webrtc/transport/nr_socket_prsock.cpp
index 0933f80851c..fcf4029506f 100644
--- a/dom/media/webrtc/transport/nr_socket_prsock.cpp
+++ b/dom/media/webrtc/transport/nr_socket_prsock.cpp
@@ -88,6 +88,7 @@ nrappkit copyright:
#include <string.h>
#include <sys/types.h>
+#include "mozilla/IceServerParser.h"
#include "mozilla/ProfilerBandwidthCounter.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/net/DNS.h"
@@ -743,6 +744,16 @@ int NrSocket::sendto(const void* msg, size_t len, int flags,
ABORT(R_WOULDBLOCK);
}
+ // Block outgoing packets to ports that are not allowed for webrtc. This runs
+ // in whatever process opened the socket -- the socket process (socket-process
+ // mtransport) or the parent process -- never the content process, which uses
+ // NrUdpSocketIpc/NrTcpSocket.
+ if (IsForbiddenAddress(to)) {
+ // Drop the packet, but report success so the caller does not retry.
+ _status = 0;
+ goto abort;
+ }
+
// TODO: Convert flags?
status = PR_SendTo(fd_, msg, len, flags, &naddr, PR_INTERVAL_NO_WAIT);
if (status < 0 || (size_t)status != len) {
@@ -808,6 +819,12 @@ int NrSocket::connect(const nr_transport_addr* addr) {
if (!fd_) ABORT(R_EOD);
+ // Block connections to ports that are not allowed for webrtc. See the note
+ // in NrSocket::sendto; this runs only in the socket/parent process.
+ if (IsForbiddenAddress(addr)) {
+ ABORT(R_WOULDBLOCK);
+ }
+
// Note: this just means we tried to connect, not that we
// are actually live.
connect_invoked_ = true;
@@ -1599,7 +1616,7 @@ abort:
}
// static
-bool NrSocketBase::IsForbiddenAddress(nr_transport_addr* addr) {
+bool NrSocketBase::IsForbiddenAddress(const nr_transport_addr* addr) {
uint16_t port;
int r;
@@ -1609,15 +1626,20 @@ bool NrSocketBase::IsForbiddenAddress(nr_transport_addr* addr) {
}
// allow auto assigned ports
- if (port != 0) {
- // Don't need to check an override scheme
- nsresult rv = NS_CheckPortSafety(port, nullptr);
- if (NS_FAILED(rv)) {
- return true;
+ if (port == 0) {
+ return false;
+ }
+
+ // First check the known good ports for webrtc.
+ for (const auto good : IceServerParser::kGoodWebrtcPortList) {
+ if (port == good) {
+ return false;
}
}
- return false;
+ // Otherwise fall back to Necko's generic outgoing port block list. Don't
+ // need to check an override scheme.
+ return NS_FAILED(NS_CheckPortSafety(port, nullptr));
}
static int nr_socket_local_destroy(void** objp) {
diff --git a/dom/media/webrtc/transport/nr_socket_prsock.h b/dom/media/webrtc/transport/nr_socket_prsock.h
index 11cfd74b16a..929c68ce814 100644
--- a/dom/media/webrtc/transport/nr_socket_prsock.h
+++ b/dom/media/webrtc/transport/nr_socket_prsock.h
@@ -99,7 +99,7 @@ class NrSocketBase {
// NrTcpSocketIpc as appropriate.
static int CreateSocket(nr_transport_addr* addr, RefPtr<NrSocketBase>* sock,
const std::shared_ptr<NrSocketProxyConfig>& config);
- static bool IsForbiddenAddress(nr_transport_addr* addr);
+ static bool IsForbiddenAddress(const nr_transport_addr* addr);
// the nr_socket APIs
virtual int create(nr_transport_addr* addr) = 0;
diff --git a/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp b/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
index 02fa550a35b..5a670996bed 100644
--- a/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
+++ b/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
@@ -425,6 +425,22 @@ TEST_F(TestNrSocketTest, SafePortAcceptedTCP) {
ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
}
+TEST_F(TestNrSocketTest, WebrtcGoodPortAcceptedUDP) {
+ nr_transport_addr address;
+ // Port 53 is on Necko's generic block list but is explicitly allowed for
+ // webrtc to allow punching through overzealous NATs.
+ ASSERT_FALSE(
+ nr_str_port_to_transport_addr("127.0.0.1", 53, IPPROTO_UDP, &address));
+ ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, WebrtcGoodPortAcceptedTCP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(
+ nr_str_port_to_transport_addr("127.0.0.1", 53, IPPROTO_TCP, &address));
+ ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
TEST_F(TestNrSocketTest, PublicConnectivity) {
CreatePublicAddrs(2);
Regression Test / PoC
diff --git a/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp b/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
index 02fa550a35b..5a670996bed 100644
--- a/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
+++ b/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
@@ -425,6 +425,22 @@ TEST_F(TestNrSocketTest, SafePortAcceptedTCP) {
ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
}
+TEST_F(TestNrSocketTest, WebrtcGoodPortAcceptedUDP) {
+ nr_transport_addr address;
+ // Port 53 is on Necko's generic block list but is explicitly allowed for
+ // webrtc to allow punching through overzealous NATs.
+ ASSERT_FALSE(
+ nr_str_port_to_transport_addr("127.0.0.1", 53, IPPROTO_UDP, &address));
+ ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, WebrtcGoodPortAcceptedTCP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(
+ nr_str_port_to_transport_addr("127.0.0.1", 53, IPPROTO_TCP, &address));
+ ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
TEST_F(TestNrSocketTest, PublicConnectivity) {
CreatePublicAddrs(2);