From 07c42006c3448110ecf079e2392b4fba15267ef0 Mon Sep 17 00:00:00 2001 From: Wenson Hsieh Date: Thu, 13 Oct 2022 15:10:43 -0700 Subject: [PATCH] Add a mechanism to remember the last IP address of the response for the main resource request https://bugs.webkit.org/show_bug.cgi?id=246128 rdar://100831206 Reviewed by John Wilander. Add a mechanism to keep track of the last IP address, corresponding to the main resource request's response; this information is stashed in a side table on the network session, alongside the existing map of first party host to CNAME. * Source/WTF/wtf/PlatformHave.h: Remove this feature flag, since the oldest macOS version we currently support (Big Sur) contains this SPI. * Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h: * Source/WebCore/platform/network/DNS.cpp: (WebCore::IPAddress::fromString): Add a helper method to create an `IPAddress` from an IP address string (IPv4 or IPv6). * Source/WebCore/platform/network/DNS.h: (WebCore::IPAddress::IPAddress): Add another explicit constructor that takes `WTF::HashTableEmptyValueType`, so that we can store `IPAddress` as a value in a `HashMap`; see below. (WTF::HashTraits::emptyValue): Additionally add a `HashTraits` template class for `IPAddress`, which allows us to store `WebCore::IPAddress` as a hash table value by implementing `emptyValue()`. * Source/WebKit/NetworkProcess/NetworkDataTask.cpp: (WebKit::NetworkDataTask::networkSession const): * Source/WebKit/NetworkProcess/NetworkDataTask.h: * Source/WebKit/NetworkProcess/NetworkProcess.cpp: (WebKit::NetworkProcess::resetParametersToDefaultValues): * Source/WebKit/NetworkProcess/NetworkSession.cpp: (WebKit::NetworkSession::setFirstPartyHostCNAMEDomain): (WebKit::NetworkSession::firstPartyHostCNAMEDomain): (WebKit::NetworkSession::resetFirstPartyDNSData): (WebKit::NetworkSession::setFirstPartyHostIPAddress): (WebKit::NetworkSession::firstPartyHostIPAddress): (WebKit::NetworkSession::resetCNAMEDomainData): Deleted. Rename `resetCNAMEDomainData` to `resetFirstPartyDNSData`, since we now use it to clear both first party CNAME information as well as first party IP address information. * Source/WebKit/NetworkProcess/NetworkSession.h: * Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.h: * Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm: (WebKit::lastRemoteIPAddress): Add a helper function to grab the last resolved IP address of the network data task; this is intended to be used after we get a response. (WebKit::NetworkDataTaskCocoa::shouldApplyCookiePolicyForThirdPartyCNAMECloaking const): (WebKit::NetworkDataTaskCocoa::updateFirstPartyInfoForSession): Update the first party IP address, alongside the first party CNAME for the main resource. (WebKit::NetworkDataTaskCocoa::applyCookiePolicyForThirdPartyCNAMECloaking): (WebKit::NetworkDataTaskCocoa::NetworkDataTaskCocoa): (WebKit::NetworkDataTaskCocoa::didReceiveResponse): (WebKit::NetworkDataTaskCocoa::willPerformHTTPRedirection): (WebKit::hasCNAMEAndCookieTransformSPI): Deleted. Remove this helper function, since we no longer need `-respondsToSelector` checks for `_resolvedCNAMEChain` and `_cookieTransformCallback`. Canonical link: https://commits.webkit.org/255507@main --- Source/WTF/wtf/PlatformHave.h | 1 - Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h | 2 - Source/WebCore/platform/network/DNS.cpp | 17 ++++++++ Source/WebCore/platform/network/DNS.h | 20 +++++++++- .../WebKit/NetworkProcess/NetworkDataTask.cpp | 5 +++ .../WebKit/NetworkProcess/NetworkDataTask.h | 1 + .../WebKit/NetworkProcess/NetworkProcess.cpp | 2 +- .../WebKit/NetworkProcess/NetworkSession.cpp | 30 ++++++++------ Source/WebKit/NetworkProcess/NetworkSession.h | 6 ++- .../cocoa/NetworkDataTaskCocoa.h | 5 +-- .../cocoa/NetworkDataTaskCocoa.mm | 39 ++++++++++--------- 11 files changed, 87 insertions(+), 41 deletions(-) diff --git a/Source/WTF/wtf/PlatformHave.h b/Source/WTF/wtf/PlatformHave.h index 751d3dad201d..134c1c734760 100644 --- a/Source/WTF/wtf/PlatformHave.h +++ b/Source/WTF/wtf/PlatformHave.h @@ -719,7 +719,6 @@ #if PLATFORM(COCOA) #define HAVE_CF_PREFS_SET_READ_ONLY 1 -#define HAVE_CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI 1 #define HAVE_CGIMAGESOURCE_WITH_ACCURATE_LOOP_COUNT 1 #define HAVE_CGIMAGESOURCE_WITH_SET_ALLOWABLE_TYPES 1 #define HAVE_HSTS_STORAGE 1 diff --git a/Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h b/Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h index 0f5f44c30ca8..148cb62913e4 100644 --- a/Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h +++ b/Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h @@ -360,12 +360,10 @@ enum : NSUInteger { NSHTTPCookieAcceptPolicyExclusivelyFromMainDocumentDomain = 3, }; -#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI) @interface NSURLSessionTask () @property (nonatomic, copy, nullable) NSArray* (^_cookieTransformCallback)(NSArray* cookies); @property (nonatomic, readonly, nullable) NSArray* _resolvedCNAMEChain; @end -#endif #endif // defined(__OBJC__) diff --git a/Source/WebCore/platform/network/DNS.cpp b/Source/WebCore/platform/network/DNS.cpp index c1122db3d867..b63a00a26ec4 100644 --- a/Source/WebCore/platform/network/DNS.cpp +++ b/Source/WebCore/platform/network/DNS.cpp @@ -30,6 +30,10 @@ #include "DNSResolveQueue.h" #include +#if OS(UNIX) +#include +#endif + namespace WebCore { void prefetchDNS(const String& hostname) @@ -55,4 +59,17 @@ void stopResolveDNS(uint64_t identifier) WebCore::DNSResolveQueue::singleton().stopResolve(identifier); } +std::optional IPAddress::fromString(const String& string) +{ + struct in6_addr addressV6; + if (inet_pton(AF_INET6, string.utf8().data(), &addressV6)) + return IPAddress { addressV6 }; + + struct in_addr addressV4; + if (inet_pton(AF_INET, string.utf8().data(), &addressV4)) + return IPAddress { addressV4 }; + + return std::nullopt; +} + } diff --git a/Source/WebCore/platform/network/DNS.h b/Source/WebCore/platform/network/DNS.h index 787be0d0e0d7..cbd26ebaeb66 100644 --- a/Source/WebCore/platform/network/DNS.h +++ b/Source/WebCore/platform/network/DNS.h @@ -28,6 +28,7 @@ #include #include #include +#include #if OS(WINDOWS) #include @@ -51,6 +52,13 @@ class IPAddress { { } + explicit IPAddress(WTF::HashTableEmptyValueType) + : m_address(WTF::HashTableEmptyValue) + { + } + + WEBCORE_EXPORT static std::optional fromString(const String&); + bool isIPv4() const { return std::holds_alternative(m_address); } bool isIPv6() const { return std::holds_alternative(m_address); } @@ -58,7 +66,7 @@ class IPAddress { const struct in6_addr& ipv6Address() const { return std::get(m_address); } private: - std::variant m_address; + std::variant m_address; }; enum class DNSError { Unknown, CannotResolve, Cancelled }; @@ -79,4 +87,12 @@ inline std::optional IPAddress::fromSockAddrIn6(const struct sockaddr return { }; } -} +} // namespace WebCore + +namespace WTF { + +template<> struct HashTraits : GenericHashTraits { + static WebCore::IPAddress emptyValue() { return WebCore::IPAddress { WTF::HashTableEmptyValue }; } +}; + +} // namespace WTF diff --git a/Source/WebKit/NetworkProcess/NetworkDataTask.cpp b/Source/WebKit/NetworkProcess/NetworkDataTask.cpp index 646a5a968415..e6ab0b8ea57a 100644 --- a/Source/WebKit/NetworkProcess/NetworkDataTask.cpp +++ b/Source/WebKit/NetworkProcess/NetworkDataTask.cpp @@ -167,6 +167,11 @@ PAL::SessionID NetworkDataTask::sessionID() const return m_session->sessionID(); } +const NetworkSession* NetworkDataTask::networkSession() const +{ + return m_session.get(); +} + NetworkSession* NetworkDataTask::networkSession() { return m_session.get(); diff --git a/Source/WebKit/NetworkProcess/NetworkDataTask.h b/Source/WebKit/NetworkProcess/NetworkDataTask.h index 8c352b93412b..422692fc131a 100644 --- a/Source/WebKit/NetworkProcess/NetworkDataTask.h +++ b/Source/WebKit/NetworkProcess/NetworkDataTask.h @@ -146,6 +146,7 @@ class NetworkDataTask : public ThreadSafeRefCounted&& completionHandler) { if (auto* session = networkSession(sessionID)) { - session->resetCNAMEDomainData(); + session->resetFirstPartyDNSData(); if (auto* resourceLoadStatistics = session->resourceLoadStatistics()) resourceLoadStatistics->resetParametersToDefaultValues(WTFMove(completionHandler)); else diff --git a/Source/WebKit/NetworkProcess/NetworkSession.cpp b/Source/WebKit/NetworkProcess/NetworkSession.cpp index 852fe3a505ce..0b6f2f6821ae 100644 --- a/Source/WebKit/NetworkProcess/NetworkSession.cpp +++ b/Source/WebKit/NetworkProcess/NetworkSession.cpp @@ -322,20 +322,14 @@ void NetworkSession::setShouldEnbleSameSiteStrictEnforcement(WebCore::SameSiteSt void NetworkSession::setFirstPartyHostCNAMEDomain(String&& firstPartyHost, WebCore::RegistrableDomain&& cnameDomain) { -#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI) ASSERT(!firstPartyHost.isEmpty() && !cnameDomain.isEmpty() && firstPartyHost != cnameDomain.string()); if (firstPartyHost.isEmpty() || cnameDomain.isEmpty() || firstPartyHost == cnameDomain.string()) return; m_firstPartyHostCNAMEDomains.add(WTFMove(firstPartyHost), WTFMove(cnameDomain)); -#else - UNUSED_PARAM(firstPartyHost); - UNUSED_PARAM(cnameDomain); -#endif } std::optional NetworkSession::firstPartyHostCNAMEDomain(const String& firstPartyHost) { -#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI) if (!decltype(m_firstPartyHostCNAMEDomains)::isValidKey(firstPartyHost)) return std::nullopt; @@ -343,17 +337,31 @@ std::optional NetworkSession::firstPartyHostCNAMEDom if (iterator == m_firstPartyHostCNAMEDomains.end()) return std::nullopt; return iterator->value; -#else - UNUSED_PARAM(firstPartyHost); - return std::nullopt; -#endif } -void NetworkSession::resetCNAMEDomainData() +void NetworkSession::resetFirstPartyDNSData() { m_firstPartyHostCNAMEDomains.clear(); + m_firstPartyHostIPAddresses.clear(); m_thirdPartyCNAMEDomainForTesting = std::nullopt; } + +void NetworkSession::setFirstPartyHostIPAddress(const String& firstPartyHost, const String& addressString) +{ + if (firstPartyHost.isEmpty() || addressString.isEmpty()) + return; + + if (auto address = WebCore::IPAddress::fromString(addressString)) + m_firstPartyHostIPAddresses.set(firstPartyHost, WTFMove(*address)); +} + +std::optional NetworkSession::firstPartyHostIPAddress(const String& firstPartyHost) +{ + if (firstPartyHost.isEmpty()) + return std::nullopt; + + return m_firstPartyHostIPAddresses.get(firstPartyHost); +} #endif // ENABLE(TRACKING_PREVENTION) void NetworkSession::storePrivateClickMeasurement(WebCore::PrivateClickMeasurement&& unattributedPrivateClickMeasurement) diff --git a/Source/WebKit/NetworkProcess/NetworkSession.h b/Source/WebKit/NetworkProcess/NetworkSession.h index 1c9fa025e0c6..341b55dc6c4d 100644 --- a/Source/WebKit/NetworkProcess/NetworkSession.h +++ b/Source/WebKit/NetworkProcess/NetworkSession.h @@ -38,6 +38,7 @@ #include "WebPageProxyIdentifier.h" #include "WebResourceLoadStatisticsStore.h" #include +#include #include #include #include @@ -136,9 +137,11 @@ class NetworkSession : public CanMakeWeakPtr { void setShouldEnbleSameSiteStrictEnforcement(WebCore::SameSiteStrictEnforcementEnabled); void setFirstPartyHostCNAMEDomain(String&& firstPartyHost, WebCore::RegistrableDomain&& cnameDomain); std::optional firstPartyHostCNAMEDomain(const String& firstPartyHost); + void setFirstPartyHostIPAddress(const String& firstPartyHost, const String& addressString); + std::optional firstPartyHostIPAddress(const String& firstPartyHost); void setThirdPartyCNAMEDomainForTesting(WebCore::RegistrableDomain&& domain) { m_thirdPartyCNAMEDomainForTesting = WTFMove(domain); }; std::optional thirdPartyCNAMEDomainForTesting() const { return m_thirdPartyCNAMEDomainForTesting; } - void resetCNAMEDomainData(); + void resetFirstPartyDNSData(); void destroyResourceLoadStatistics(CompletionHandler&&); #endif @@ -269,6 +272,7 @@ class NetworkSession : public CanMakeWeakPtr { WebCore::FirstPartyWebsiteDataRemovalMode m_firstPartyWebsiteDataRemovalMode { WebCore::FirstPartyWebsiteDataRemovalMode::AllButCookies }; WebCore::RegistrableDomain m_standaloneApplicationDomain; HashMap m_firstPartyHostCNAMEDomains; + HashMap m_firstPartyHostIPAddresses; std::optional m_thirdPartyCNAMEDomainForTesting; #endif bool m_isStaleWhileRevalidateEnabled { false }; diff --git a/Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.h b/Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.h index 8d021e3921f6..c1088ad6b98c 100644 --- a/Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.h +++ b/Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.h @@ -100,10 +100,9 @@ class NetworkDataTaskCocoa final : public NetworkDataTask { #if ENABLE(TRACKING_PREVENTION) static NSHTTPCookieStorage *statelessCookieStorage(); -#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI) void updateFirstPartyInfoForSession(const URL&); + bool shouldApplyCookiePolicyForThirdPartyCNAMECloaking() const; void applyCookiePolicyForThirdPartyCNAMECloaking(const WebCore::ResourceRequest&); -#endif void blockCookies(); void unblockCookies(); bool needsFirstPartyCookieBlockingLatchModeQuirk(const URL& firstPartyURL, const URL& requestURL, const URL& redirectingURL) const; @@ -120,9 +119,7 @@ class NetworkDataTaskCocoa final : public NetworkDataTask { #if ENABLE(TRACKING_PREVENTION) bool m_hasBeenSetToUseStatelessCookieStorage { false }; -#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI) Seconds m_ageCapForCNAMECloakedCookies { 24_h * 7 }; -#endif #endif bool m_isForMainResourceNavigationForAnyFrame { false }; diff --git a/Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm b/Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm index 0bf5f53f09aa..b16e082a2d9a 100644 --- a/Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm +++ b/Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm @@ -66,6 +66,13 @@ void enableNetworkConnectionIntegrity(NSMutableURLRequest *) { } namespace WebKit { +static NSString *lastRemoteIPAddress(NSURLSessionDataTask *task) +{ + // FIXME (246428): In a future patch, this should adopt CFNetwork API that retrieves the original + // IP address of the proxied response, rather than the proxy itself. + return task._incompleteTaskMetrics.transactionMetrics.lastObject.remoteAddress; +} + void setPCMDataCarriedOnRequest(WebCore::PrivateClickMeasurement::PcmDataCarried pcmDataCarried, NSMutableURLRequest *request) { processPCMRequest(pcmDataCarried, request); @@ -140,15 +147,6 @@ static float toNSURLSessionTaskPriority(WebCore::ResourceLoadPriority priority) return statelessCookieStorage.get().get(); } -#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI) -// FIXME: Remove these selector checks when macOS Big Sur has shipped. -// https://bugs.webkit.org/show_bug.cgi?id=215280 -static bool hasCNAMEAndCookieTransformSPI(NSURLSessionDataTask* task) -{ - return [task respondsToSelector:@selector(_cookieTransformCallback)] - && [task respondsToSelector:@selector(_resolvedCNAMEChain)]; -} - static WebCore::RegistrableDomain lastCNAMEDomain(NSArray *cnames) { if (auto* lastResolvedCNAMEInChain = [cnames lastObject]) { @@ -161,19 +159,29 @@ static bool hasCNAMEAndCookieTransformSPI(NSURLSessionDataTask* task) return { }; } +bool NetworkDataTaskCocoa::shouldApplyCookiePolicyForThirdPartyCNAMECloaking() const +{ + auto* session = networkSession(); + return session && session->networkStorageSession() && session->networkStorageSession()->resourceLoadStatisticsEnabled(); +} + void NetworkDataTaskCocoa::updateFirstPartyInfoForSession(const URL& requestURL) { - if (!hasCNAMEAndCookieTransformSPI(m_task.get()) || !networkSession() || !networkSession()->networkStorageSession() || !networkSession()->networkStorageSession()->resourceLoadStatisticsEnabled() || requestURL.host().isEmpty()) + if (!shouldApplyCookiePolicyForThirdPartyCNAMECloaking() || requestURL.host().isEmpty()) return; + auto* session = networkSession(); auto cnameDomain = lastCNAMEDomain([m_task _resolvedCNAMEChain]); if (!cnameDomain.isEmpty()) - networkSession()->setFirstPartyHostCNAMEDomain(requestURL.host().toString(), WTFMove(cnameDomain)); + session->setFirstPartyHostCNAMEDomain(requestURL.host().toString(), WTFMove(cnameDomain)); + + if (NSString *ipAddress = lastRemoteIPAddress(m_task.get()); ipAddress.length) + session->setFirstPartyHostIPAddress(requestURL.host().toString(), ipAddress); } void NetworkDataTaskCocoa::applyCookiePolicyForThirdPartyCNAMECloaking(const WebCore::ResourceRequest& request) { - if (!hasCNAMEAndCookieTransformSPI(m_task.get()) || isTopLevelNavigation() || !networkSession() || !networkSession()->networkStorageSession() || !networkSession()->networkStorageSession()->resourceLoadStatisticsEnabled()) + if (isTopLevelNavigation() || !shouldApplyCookiePolicyForThirdPartyCNAMECloaking()) return; if (isThirdPartyRequest(request)) { @@ -222,7 +230,6 @@ static bool hasCNAMEAndCookieTransformSPI(NSURLSessionDataTask* task) return cookiesSetInResponse; }).get(); } -#endif void NetworkDataTaskCocoa::blockCookies() { @@ -410,9 +417,7 @@ static inline bool computeIsAlwaysOnLoggingAllowed(NetworkSession& session) } #if ENABLE(TRACKING_PREVENTION) -#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI) applyCookiePolicyForThirdPartyCNAMECloaking(request); -#endif if (shouldBlockCookies) { #if !RELEASE_LOG_DISABLED if (m_session->shouldLogCookieInformation()) @@ -499,10 +504,8 @@ static inline bool computeIsAlwaysOnLoggingAllowed(NetworkSession& session) void NetworkDataTaskCocoa::didReceiveResponse(WebCore::ResourceResponse&& response, NegotiatedLegacyTLS negotiatedLegacyTLS, PrivateRelayed privateRelayed, WebKit::ResponseCompletionHandler&& completionHandler) { WTFEmitSignpost(m_task.get(), "DataTask", "received response headers"); -#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI) if (isTopLevelNavigation()) updateFirstPartyInfoForSession(response.url()); -#endif NetworkDataTask::didReceiveResponse(WTFMove(response), negotiatedLegacyTLS, privateRelayed, WTFMove(completionHandler)); } @@ -575,9 +578,7 @@ static inline bool computeIsAlwaysOnLoggingAllowed(NetworkSession& session) #endif #if ENABLE(TRACKING_PREVENTION) -#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI) applyCookiePolicyForThirdPartyCNAMECloaking(request); -#endif if (!m_hasBeenSetToUseStatelessCookieStorage) { if (m_storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::EphemeralStateless || (m_session->networkStorageSession() && m_session->networkStorageSession()->shouldBlockCookies(request, m_frameID, m_pageID, m_shouldRelaxThirdPartyCookieBlocking)))