Medium firefox Info Disclosure 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionInformation disclosure in the WebRTC component
ComponentDOM
Bug ClassInfo Disclosure
Tracker2045368
Fix commit1684bd5486af (firefox) +46/-8
CISA KEVNot listed
CreditedTomoya Nakanishi
Disclosed2026-08-18

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 NrSocketBase helper that decides whether a destination nr_transport_addr targets 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.

Key insight
The core mistake was that the port-forbidding check existed but was never invoked on the send/connect egress paths, so the block list was effectively dead code for outbound WebRTC traffic; the fix wires IsForbiddenAddress into both NrSocket::sendto and NrSocket::connect so the guard actually runs before any packet leaves.

Attack Path

  1. Craft candidates A malicious page drives a WebRTC session and supplies ICE candidates pointing at restricted local or internal destination ports.
  2. Reach the socket process Candidate gathering causes NrSocket in the socket/parent process to attempt connect or sendto toward those addresses.
  3. Bypass the block list Because neither method called IsForbiddenAddress, the NS_CheckPortSafety restriction was skipped and packets were emitted to otherwise-forbidden ports.
  4. 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

An attacker gains the ability to direct WebRTC UDP/TCP traffic to restricted ports and infer service or network state from the results, a moderate information-disclosure primitive rather than memory corruption. The abused code runs in the socket process or parent process (never the content process, which uses 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

FunctionChangeNotes
if
dom/media/webrtc/transport/nr_socket_prsock.cpp
modified
for
dom/media/webrtc/transport/nr_socket_prsock.cpp
modified
TEST_F
dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
modified

Files Changed

  • dom/media/webrtc/transport/nr_socket_prsock.cpp
  • dom/media/webrtc/transport/nr_socket_prsock.h
  • dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp

Audit Directions

  • Guard-defined-but-never-called
    Search for safety helpers like IsForbiddenAddress that exist as static utilities but are not invoked on the actual I/O path they are meant to protect.
  • Egress choke points
    Verify that every sendto/connect/bind implementation across mtransport socket classes (NrUdpSocketIpc, NrTcpSocket, and proxied variants) enforces the same port-safety policy, not just one class.
  • Allowlist drift
    Review IceServerParser::kGoodWebrtcPortList and NS_CheckPortSafety interactions to ensure newly allowed WebRTC ports do not silently re-open cross-protocol abuse vectors.
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);
 
Loading diff…

Regression Test / PoC

shipped with the fix
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);
Loading diff…