Skip to content

Commit

Permalink
Add a mechanism to remember the last IP address of the response for t…
Browse files Browse the repository at this point in the history
…he 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<WebCore::IPAddress>::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
  • Loading branch information
whsieh committed Oct 13, 2022
1 parent 577d060 commit 07c4200
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 41 deletions.
1 change: 0 additions & 1 deletion Source/WTF/wtf/PlatformHave.h
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h
Expand Up @@ -360,12 +360,10 @@ enum : NSUInteger {
NSHTTPCookieAcceptPolicyExclusivelyFromMainDocumentDomain = 3,
};

#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI)
@interface NSURLSessionTask ()
@property (nonatomic, copy, nullable) NSArray<NSHTTPCookie*>* (^_cookieTransformCallback)(NSArray<NSHTTPCookie*>* cookies);
@property (nonatomic, readonly, nullable) NSArray<NSString*>* _resolvedCNAMEChain;
@end
#endif

#endif // defined(__OBJC__)

Expand Down
17 changes: 17 additions & 0 deletions Source/WebCore/platform/network/DNS.cpp
Expand Up @@ -30,6 +30,10 @@
#include "DNSResolveQueue.h"
#include <wtf/MainThread.h>

#if OS(UNIX)
#include <arpa/inet.h>
#endif

namespace WebCore {

void prefetchDNS(const String& hostname)
Expand All @@ -55,4 +59,17 @@ void stopResolveDNS(uint64_t identifier)
WebCore::DNSResolveQueue::singleton().stopResolve(identifier);
}

std::optional<IPAddress> 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;
}

}
20 changes: 18 additions & 2 deletions Source/WebCore/platform/network/DNS.h
Expand Up @@ -28,6 +28,7 @@
#include <optional>
#include <variant>
#include <wtf/Forward.h>
#include <wtf/HashTraits.h>

#if OS(WINDOWS)
#include <winsock2.h>
Expand All @@ -51,14 +52,21 @@ class IPAddress {
{
}

explicit IPAddress(WTF::HashTableEmptyValueType)
: m_address(WTF::HashTableEmptyValue)
{
}

WEBCORE_EXPORT static std::optional<IPAddress> fromString(const String&);

bool isIPv4() const { return std::holds_alternative<struct in_addr>(m_address); }
bool isIPv6() const { return std::holds_alternative<struct in6_addr>(m_address); }

const struct in_addr& ipv4Address() const { return std::get<struct in_addr>(m_address); }
const struct in6_addr& ipv6Address() const { return std::get<struct in6_addr>(m_address); }

private:
std::variant<struct in_addr, struct in6_addr> m_address;
std::variant<WTF::HashTableEmptyValueType, struct in_addr, struct in6_addr> m_address;
};

enum class DNSError { Unknown, CannotResolve, Cancelled };
Expand All @@ -79,4 +87,12 @@ inline std::optional<IPAddress> IPAddress::fromSockAddrIn6(const struct sockaddr
return { };
}

}
} // namespace WebCore

namespace WTF {

template<> struct HashTraits<WebCore::IPAddress> : GenericHashTraits<WebCore::IPAddress> {
static WebCore::IPAddress emptyValue() { return WebCore::IPAddress { WTF::HashTableEmptyValue }; }
};

} // namespace WTF
5 changes: 5 additions & 0 deletions Source/WebKit/NetworkProcess/NetworkDataTask.cpp
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions Source/WebKit/NetworkProcess/NetworkDataTask.h
Expand Up @@ -146,6 +146,7 @@ class NetworkDataTask : public ThreadSafeRefCounted<NetworkDataTask, WTF::Destru

PAL::SessionID sessionID() const;

const NetworkSession* networkSession() const;
NetworkSession* networkSession();

protected:
Expand Down
2 changes: 1 addition & 1 deletion Source/WebKit/NetworkProcess/NetworkProcess.cpp
Expand Up @@ -747,7 +747,7 @@ void NetworkProcess::getResourceLoadStatisticsDataSummary(PAL::SessionID session
void NetworkProcess::resetParametersToDefaultValues(PAL::SessionID sessionID, CompletionHandler<void()>&& completionHandler)
{
if (auto* session = networkSession(sessionID)) {
session->resetCNAMEDomainData();
session->resetFirstPartyDNSData();
if (auto* resourceLoadStatistics = session->resourceLoadStatistics())
resourceLoadStatistics->resetParametersToDefaultValues(WTFMove(completionHandler));
else
Expand Down
30 changes: 19 additions & 11 deletions Source/WebKit/NetworkProcess/NetworkSession.cpp
Expand Up @@ -322,38 +322,46 @@ 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<WebCore::RegistrableDomain> NetworkSession::firstPartyHostCNAMEDomain(const String& firstPartyHost)
{
#if HAVE(CFNETWORK_CNAME_AND_COOKIE_TRANSFORM_SPI)
if (!decltype(m_firstPartyHostCNAMEDomains)::isValidKey(firstPartyHost))
return std::nullopt;

auto iterator = m_firstPartyHostCNAMEDomains.find(firstPartyHost);
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<WebCore::IPAddress> 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)
Expand Down
6 changes: 5 additions & 1 deletion Source/WebKit/NetworkProcess/NetworkSession.h
Expand Up @@ -38,6 +38,7 @@
#include "WebPageProxyIdentifier.h"
#include "WebResourceLoadStatisticsStore.h"
#include <WebCore/BlobRegistryImpl.h>
#include <WebCore/DNS.h>
#include <WebCore/FetchIdentifier.h>
#include <WebCore/NetworkStorageSession.h>
#include <WebCore/PrivateClickMeasurement.h>
Expand Down Expand Up @@ -136,9 +137,11 @@ class NetworkSession : public CanMakeWeakPtr<NetworkSession> {
void setShouldEnbleSameSiteStrictEnforcement(WebCore::SameSiteStrictEnforcementEnabled);
void setFirstPartyHostCNAMEDomain(String&& firstPartyHost, WebCore::RegistrableDomain&& cnameDomain);
std::optional<WebCore::RegistrableDomain> firstPartyHostCNAMEDomain(const String& firstPartyHost);
void setFirstPartyHostIPAddress(const String& firstPartyHost, const String& addressString);
std::optional<WebCore::IPAddress> firstPartyHostIPAddress(const String& firstPartyHost);
void setThirdPartyCNAMEDomainForTesting(WebCore::RegistrableDomain&& domain) { m_thirdPartyCNAMEDomainForTesting = WTFMove(domain); };
std::optional<WebCore::RegistrableDomain> thirdPartyCNAMEDomainForTesting() const { return m_thirdPartyCNAMEDomainForTesting; }
void resetCNAMEDomainData();
void resetFirstPartyDNSData();
void destroyResourceLoadStatistics(CompletionHandler<void()>&&);
#endif

Expand Down Expand Up @@ -269,6 +272,7 @@ class NetworkSession : public CanMakeWeakPtr<NetworkSession> {
WebCore::FirstPartyWebsiteDataRemovalMode m_firstPartyWebsiteDataRemovalMode { WebCore::FirstPartyWebsiteDataRemovalMode::AllButCookies };
WebCore::RegistrableDomain m_standaloneApplicationDomain;
HashMap<String, WebCore::RegistrableDomain> m_firstPartyHostCNAMEDomains;
HashMap<String, WebCore::IPAddress> m_firstPartyHostIPAddresses;
std::optional<WebCore::RegistrableDomain> m_thirdPartyCNAMEDomainForTesting;
#endif
bool m_isStaleWhileRevalidateEnabled { false };
Expand Down
5 changes: 1 addition & 4 deletions Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.h
Expand Up @@ -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;
Expand All @@ -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 };
Expand Down
39 changes: 20 additions & 19 deletions Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm
Expand Up @@ -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);
Expand Down Expand Up @@ -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<NSString *> *cnames)
{
if (auto* lastResolvedCNAMEInChain = [cnames lastObject]) {
Expand All @@ -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)) {
Expand Down Expand Up @@ -222,7 +230,6 @@ static bool hasCNAMEAndCookieTransformSPI(NSURLSessionDataTask* task)
return cookiesSetInResponse;
}).get();
}
#endif

void NetworkDataTaskCocoa::blockCookies()
{
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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)))
Expand Down

0 comments on commit 07c4200

Please sign in to comment.