diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp index 235d5dde8f..d0102a6746 100644 --- a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp @@ -116,6 +116,8 @@ #include "mozilla/dom/BrowserChild.h" #include "mozilla/net/WebrtcProxyConfig.h" +#include "MaskConfig.hpp" +#include "mozilla/RustRegex.h" #ifdef XP_WIN // We need to undef the MS macro again in case the windows include file @@ -1702,7 +1704,15 @@ PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) { } }; - mLocalRequestedSDP = aSDP; + // Sanitize the SDP to prevent IP leaks + std::string sanitizedSDP(aSDP); + nsresult sanitizeResult = SanitizeSDPForIPLeak(sanitizedSDP); + if (NS_FAILED(sanitizeResult)) { + CSFLogError(LOGTAG, "%s - Failed to sanitize SDP", __FUNCTION__); + return sanitizeResult; + } + + mLocalRequestedSDP = sanitizedSDP; SyncToJsep(); @@ -3224,12 +3234,22 @@ const std::string& PeerConnectionImpl::GetName() { return mName; } -void PeerConnectionImpl::CandidateReady(const std::string& candidate, +void PeerConnectionImpl::CandidateReady(const std::string& candidate_, const std::string& transportId, const std::string& ufrag) { STAMP_TIMECARD(mTimeCard, "Ice Candidate gathered"); PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + const std::string& candidate = [&]() -> const std::string& { + if (ShouldSpoofCandidateIP()) { + static std::string spoofedCandidate; + spoofedCandidate = SpoofCandidateIP(candidate_); + return spoofedCandidate; + } else { + return candidate_; + } + }(); + if (mForceIceTcp && std::string::npos != candidate.find(" UDP ")) { CSFLogWarn(LOGTAG, "Blocking local UDP candidate: %s", candidate.c_str()); STAMP_TIMECARD(mTimeCard, "UDP Ice Candidate blocked"); @@ -3292,10 +3312,184 @@ void PeerConnectionImpl::SendLocalIceCandidateToContent( uint16_t level, const std::string& mid, const std::string& candidate, const std::string& ufrag) { STAMP_TIMECARD(mTimeCard, "Send Ice Candidate to content"); - JSErrorResult rv; - mPCObserver->OnIceCandidate(level, ObString(mid.c_str()), - ObString(candidate.c_str()), - ObString(ufrag.c_str()), rv); + + // Check if IP spoofing is enabled + if (ShouldSpoofCandidateIP()) { + std::string spoofedCandidate = SpoofCandidateIP(candidate); + JSErrorResult rv; + mPCObserver->OnIceCandidate(level, ObString(mid.c_str()), + ObString(spoofedCandidate.c_str()), + ObString(ufrag.c_str()), rv); + } else { + JSErrorResult rv; + mPCObserver->OnIceCandidate(level, ObString(mid.c_str()), + ObString(candidate.c_str()), + ObString(ufrag.c_str()), rv); + } +} + +bool PeerConnectionImpl::ShouldSpoofCandidateIP() const { + // Checks if either webrtc:ipv4 or webrtc:ipv6 is set in the config + return MaskConfig::GetString("webrtc:ipv4").has_value() || + MaskConfig::GetString("webrtc:ipv6").has_value() || + MaskConfig::GetString("webrtc:localipv4").has_value() || + MaskConfig::GetString("webrtc:localipv6").has_value(); +} + +// ***** +// Helper functions to determine if IPs should be masked or not +// ***** + +bool isSpecialIP(const std::string& ip) { + // Check if an IP should not be masked (special addresses) + return (ip == "0.0.0.0" || ip == "127.0.0.1" || + ip == "::" || ip == "::1" || + ip.compare(0, 8, "169.254.") == 0 || + ip.compare(0, 4, "fe80") == 0); +} + +bool isPrivateIP(const std::string& ip) { + // Check if an IP is on a private network + bool isIPv6 = (ip.find(':') != std::string::npos); + + if (isIPv6) { + // check for private ipv6 (fc00::/7) + return (ip.length() >= 4 && + ((ip[0] == 'f' || ip[0] == 'F') && + (ip[1] == 'c' || ip[1] == 'd' || + ip[1] == 'C' || ip[1] == 'D'))); + } else { + // check ipv4 private ranges (RFC1918) + return (ip.compare(0, 8, "192.168.") == 0 || + ip.compare(0, 3, "10.") == 0 || + (ip.compare(0, 4, "172.") == 0 && + ip.length() >= 7 && + ip[4] >= '1' && ip[4] <= '3' && + (ip[5] >= '0' && ip[5] <= '9'))); + } +} + +std::string getMaskForIP(const std::string& ip) { + // Get the corresponding mask for an ip address + if (isSpecialIP(ip)) { + return ip; // dont mask special IPs + } + bool isIPv6 = (ip.find(':') != std::string::npos); + bool isPrivate = isPrivateIP(ip); + auto ipv4Value = MaskConfig::GetString("webrtc:ipv4"); + auto ipv6Value = MaskConfig::GetString("webrtc:ipv6"); + auto localIpv4Value = MaskConfig::GetString("webrtc:localipv4"); + auto localIpv6Value = MaskConfig::GetString("webrtc:localipv6"); + + if (isIPv6) { + if (isPrivate && localIpv6Value) { + return localIpv6Value.value(); + } else if (ipv6Value) { + return ipv6Value.value(); + } + } else { + if (isPrivate && localIpv4Value) { + return localIpv4Value.value(); + } else if (ipv4Value) { + return ipv4Value.value(); + } + } + // return original ip if no mask is available + return ip; +} + +std::string replaceIPAddresses( + const std::string& input, const char* pattern) { + // Replace IP addresses in a output line + mozilla::RustRegex regex(pattern); + if (!regex.IsValid()) { + return input; + } + + std::string result; + auto iter = regex.IterMatches(input); + size_t lastEnd = 0; + + while (auto match = iter.Next()) { + std::string ip = input.substr(match->start, match->end - match->start); + std::string mask = getMaskForIP(ip); + + if (mask != ip) { + result.append(input, lastEnd, match->start - lastEnd); + result.append(mask); + } else { + result.append(input, lastEnd, match->end - lastEnd); + } + + lastEnd = match->end; + } + + result.append(input, lastEnd); + return result; +} + +std::string PeerConnectionImpl::SpoofCandidateIP(const std::string& candidate) { + // Helper to spoof IPs in candidate strings + if (!ShouldSpoofCandidateIP() || candidate.empty()) { + return candidate; + } + // ipv4 + const char* ipv4Pattern = "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; + std::string result = replaceIPAddresses(candidate, ipv4Pattern); + // ipv6 + const char* ipv6Pattern = "(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}"; + result = replaceIPAddresses(result, ipv6Pattern); + + return result; +} + +nsresult PeerConnectionImpl::SanitizeSDPForIPLeak(std::string& sdp) { + // Sanitize sdp + auto ipv4Value = MaskConfig::GetString("webrtc:ipv4"); + auto ipv6Value = MaskConfig::GetString("webrtc:ipv6"); + auto localIpv4Value = MaskConfig::GetString("webrtc:localipv4"); + auto localIpv6Value = MaskConfig::GetString("webrtc:localipv6"); + + if (!ipv4Value && !ipv6Value && !localIpv4Value && !localIpv6Value) { + return NS_OK; + } + + // process the SDP line by line to handle candidate lines + std::istringstream iss(sdp); + std::ostringstream oss; + std::string line; + + while (std::getline(iss, line)) { + // process candidates and other lines that might contain IPs + if (line.compare(0, 12, "a=candidate:") == 0) { + std::string processed = SpoofCandidateIP(line); + if (processed != line) { + line = processed; + } + } + // add line back with proper line ending + if (line.empty() || (line.back() != '\r' && line.back() != '\n')) { + oss << line << "\r\n"; + } else { + oss << line; + } + } + // process the entire SDP for any remaining IP addresses + std::string processedSdp = oss.str(); + std::string originalSdp = processedSdp; + + // ipv4 + const char* ipv4Pattern = "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; + processedSdp = replaceIPAddresses(processedSdp, ipv4Pattern); + // ipv6 + const char* ipv6Pattern = "(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}"; + processedSdp = replaceIPAddresses(processedSdp, ipv6Pattern); + + if (processedSdp != originalSdp) { + sdp = processedSdp; + } + + return NS_OK; } void PeerConnectionImpl::IceConnectionStateChange( @@ -3625,11 +3819,27 @@ void PeerConnectionImpl::UpdateDefaultCandidate( const std::string& defaultRtcpAddr, uint16_t defaultRtcpPort, const std::string& transportId) { CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + + // sanitize the default candidate addresses if IP spoofing is enabled + std::string sanitizedDefaultAddr = defaultAddr; + std::string sanitizedDefaultRtcpAddr = defaultRtcpAddr; + + if (ShouldSpoofCandidateIP()) { + // apply masking + if (!defaultAddr.empty()) { + sanitizedDefaultAddr = SpoofCandidateIP(defaultAddr); + } + if (!defaultRtcpAddr.empty()) { + sanitizedDefaultRtcpAddr = SpoofCandidateIP(defaultRtcpAddr); + } + } + + // use the sanitized addresses mJsepSession->UpdateDefaultCandidate( - defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort, transportId); + sanitizedDefaultAddr, defaultPort, sanitizedDefaultRtcpAddr, defaultRtcpPort, transportId); if (mUncommittedJsepSession) { mUncommittedJsepSession->UpdateDefaultCandidate( - defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort, + sanitizedDefaultAddr, defaultPort, sanitizedDefaultRtcpAddr, defaultRtcpPort, transportId); } } @@ -4399,8 +4609,10 @@ bool PeerConnectionImpl::GetPrefDefaultAddressOnly() const { uint64_t winId = mWindow->WindowID(); - bool default_address_only = Preferences::GetBool( - "media.peerconnection.ice.default_address_only", false); + bool default_address_only = + Preferences::GetBool("media.peerconnection.ice.default_address_only", + false) || + ShouldSpoofCandidateIP(); default_address_only |= !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId); return default_address_only; diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.h b/dom/media/webrtc/jsapi/PeerConnectionImpl.h index 138cc07bd9..91cc12f87e 100644 --- a/dom/media/webrtc/jsapi/PeerConnectionImpl.h +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.h @@ -621,8 +621,12 @@ class PeerConnectionImpl final RefPtr GetMediaPipelineForTrack( dom::MediaStreamTrack& aRecvTrack); - void CandidateReady(const std::string& candidate, + void CandidateReady(const std::string& candidate_, const std::string& transportId, const std::string& ufrag); + + bool ShouldSpoofCandidateIP() const; + nsresult SanitizeSDPForIPLeak(std::string& sdp); + std::string SpoofCandidateIP(const std::string& candidate); void SendLocalIceCandidateToContent(uint16_t level, const std::string& mid, const std::string& candidate, const std::string& ufrag); diff --git a/dom/media/webrtc/jsapi/moz.build b/dom/media/webrtc/jsapi/moz.build index 925190505a..878853f0f0 100644 --- a/dom/media/webrtc/jsapi/moz.build +++ b/dom/media/webrtc/jsapi/moz.build @@ -62,3 +62,6 @@ EXPORTS.mozilla.dom += [ ] FINAL_LIBRARY = "xul" + +# DOM Mask +LOCAL_INCLUDES += ["/omegacfg"] \ No newline at end of file