8000 The Clear-Site-Data HTTP header should obey origin partition · WebKit/WebKit@4729541 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4729541

Browse files
committed
The Clear-Site-Data HTTP header should obey origin partition
https://bugs.webkit.org/show_bug.cgi?id=251094 Reviewed by Youenn Fablet. The Clear-Site-Data HTTP header should obey origin partition. If shouldn't be possible for an iframe of origin A under top origin B to be able to clear site data from top origin A (and vice-versa). Our storages are partitioned and the request to clear site data should respect that. * LayoutTests/http/wpt/clear-site-data/partitioning-expected.txt: Added. * LayoutTests/http/wpt/clear-site-data/partitioning.html: Added. * LayoutTests/http/wpt/clear-site-data/resources/partitioning-popup.html: Added. * LayoutTests/imported/w3c/web-platform-tests/clear-site-data/support/test_utils.sub.js: * Source/WebCore/loader/cache/MemoryCache.cpp: (WebCore::MemoryCache::removeResourcesWithOrigin): * Source/WebCore/loader/cache/MemoryCache.h: * Source/WebCore/platform/network/NetworkStorageSession.cpp: (WebCore::NetworkStorageSession::deleteCookies): * Source/WebCore/platform/network/NetworkStorageSession.h: * Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm: (WebCore::NetworkStorageSession::deleteCookiesMatching): (WebCore::NetworkStorageSession::deleteCookies): (WebCore::NetworkStorageSession::deleteCookiesForHostnames): * Source/WebCore/workers/service/ServiceWorkerRegistrationKey.cpp: (WebCore::ServiceWorkerRegistrationKey::matchesOriginWithCachePartition const): * Source/WebCore/workers/service/ServiceWorkerRegistrationKey.h: * Source/WebCore/workers/service/server/SWServer.cpp: (WebCore::SWServer::clear): (WebCore::SWServer::clearInternal): * Source/WebCore/workers/service/server/SWServer.h: * Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp: (WebKit::NetworkConnectionToWebProcess::deleteWebsiteDataForOrigin): (WebKit::NetworkConnectionToWebProcess::deleteWebsiteDataForOrigins): Deleted. * Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h: * Source/WebKit/NetworkProcess/NetworkProcess.cpp: (WebKit::NetworkProcess::deleteWebsiteDataForOrigin): * Source/WebKit/NetworkProcess/NetworkProcess.h: * Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp: (WebKit::NetworkResourceLoader::processClearSiteDataHeader): * Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp: (WebKit::NetworkStorageManager::deleteData): * Source/WebKit/NetworkProcess/storage/NetworkStorageManager.h: * Source/WebKit/WebProcess/Network/NetworkProcessConnection.cpp: (WebKit::NetworkProcessConnection::deleteWebsiteDataForOrigin): (WebKit::NetworkProcessConnection::deleteWebsiteDataForOrigins): Deleted. * Source/WebKit/WebProcess/Network/NetworkProcessConnection.h: * Source/WebKit/WebProcess/Network/NetworkProcessConnection.messages.in: * Source/WebKit/WebProcess/WebProcess.cpp: (WebKit::WebProcess::deleteWebsiteDataForOrigin): * Source/WebKit/WebProcess/WebProcess.h: Canonical link: https://commits.webkit.org/259466@main
1 parent 92fa4f2 commit 4729541

27 files changed

+286
-56
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
PASS Clear datatypes on navigation:
3+
PASS Clear datatypes on navigation: cookies
4+
PASS Clear datatypes on navigation: storage
5+
PASS Clear datatypes on navigation: cookies, storage
6+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
<script src="/common/get-host-info.sub.js"></script>
7+
<script src="/clear-site-data/support/test_utils.sub.js"></script>
8+
</head>
9+
<body>
10+
<script>
11+
/**
12+
* @param Array.<Array.<Datatype>> combination A combination of datatypes.
13+
* @param Dict.<string, boolean> report A map between a datatype name and
14+
* whether it is empty.
15+
* @return boolean Whether all datatypes are empty if and only if they are
16+
* included in the |combination|.
17+
*/
18+
function verifyDatatypes(combination, report) {
19+
TestUtils.DATATYPES.forEach(function(datatype) {
20+
if (combination.indexOf(datatype) != -1) {
21+
assert_true(report[datatype.name], datatype.name + " should have been cleared.");
22+
} else {
23+
assert_false(report[datatype.name], datatype.name + " should NOT have been cleared.");
24+
}
25+
});
26+
}
27+
28+
TestUtils.COMBINATIONS.forEach(function(combination) {
29+
var test_name = "Clear datatypes on navigation: " +
30+
combination.map(function(e) { return e.name; }).join(", ");
31+
32+
promise_test(function(test) {
33+
return new Promise(function(resolve_test, reject_test) {
34+
TestUtils.populateDatatypes().then(function() {
35+
return new Promise(function(resolve, reject) {
36+
window.addEventListener("message", resolve);
37+
let names = combination.map(function(e) { return e.name });
38+
// Open a cross-origin popup with an iframe that is same-origin with us and serves the Clear-Site-Data header.
39+
// Even though same origin with us, it shouldn't clear our data because of origin partitioning.
40+
open(get_host_info().REMOTE_ORIGIN + "/WebKit/clear-site-data/resources/partitioning-popup.html?" + names.join("&"));
41+
}).then(function(messageEvent) {
42+
// The same-origin frame under a cross-origin parent cleared site data. Check that our data didn't go away.
43+
var report = {};
44+
45+
Promise.all(TestUtils.DATATYPES.map(function(datatype) {
46+
return datatype.isEmpty().then(function(isEmpty) {
47+
report[datatype.name] = isEmpty;
48+
});
49+
})).then(() => {
50+
verifyDatatypes([], report);
51+
resolve_test();
52+
});
53+
});
54+
});
55+
});
56+
}, test_name);
57+
});
58+
</script>
59+
</body>
60+
</html>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<body>
2+
<script src="/common/get-host-info.sub.js"></script>
3+
<script>
4+
// Forward messages from iframe to opener.
5+
window.addEventListener("message", (e) => {
6+
opener.postMessage(e.data, "*");
7+
});
8+
onload = () => {
9+
let iframe = document.createElement("iframe");
10+
iframe.src = get_host_info().ORIGIN + "/clear-site-data/support/echo-clear-site-data.py" + location.search;
11+
document.body.appendChild(iframe);
12+
};
13+
</script>
14+
</body>

LayoutTests/platform/glib/TestExpectations

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2966,6 +2966,9 @@ http/tests/webgpu/webgpu/api/operation/memory_sync/buffer/multiple_buffers.html
29662966

29672967
webkit.org/b/245324 http/tests/notifications/notification.html [ Crash ]
29682968

2969+
# NetworkStorageSession::deleteCookies() doesn't obey partitioning for glib ports.
2970+
http/wpt/clear-site-data/partitioning.html [ Skip ]
2971+
29692972
# Failure since import.
29702973
webkit.org/b/243614 imported/w3c/web-platform-tests/IndexedDB/idb-partitioned-coverage.tentative.sub.html [ Failure ]
29712974
webkit.org/b/243614 imported/w3c/web-platform-tests/IndexedDB/idbobjectstore_batchGetAll_largeValue.tentative.any.html [ Failure ]

Source/WebCore/loader/cache/MemoryCache.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "CachedImageClient.h"
2929
#include "CachedResource.h"
3030
#include "CachedResourceHandle.h"
31+
#include "ClientOrigin.h"
3132
#include "Document.h"
3233
#include "FrameLoader.h"
3334
#include "FrameLoaderTypes.h"
@@ -484,16 +485,14 @@ void MemoryCache::resourceAccessed(CachedResource& resource)
484485
insertInLRUList(resource);
485486
}
486487

487-
void MemoryCache::removeResourcesWithOrigin(SecurityOrigin& origin)
488+
void MemoryCache::removeResourcesWithOrigin(const SecurityOrigin& origin, const String& cachePartition)
488489
{
489-
String originPartition = ResourceRequest::partitionName(origin.host());
490-
491490
Vector<CachedResource*> resourcesWithOrigin;
492491
for (auto& resources : m_sessionResources.values()) {
493492
for (auto& keyValue : *resources) {
494493
auto& resource = *keyValue.value;
495494
auto& partitionName = keyValue.key.second;
496-
if (partitionName == originPartition) {
495+
if (partitionName == cachePartition) {
497496
resourcesWithOrigin.append(&resource);
498497
continue;
499498
}
@@ -507,6 +506,18 @@ void MemoryCache::removeResourcesWithOrigin(SecurityOrigin& origin)
507506
remove(*resource);
508507
}
509508

509+
void MemoryCache::removeResourcesWithOrigin(const SecurityOrigin& origin)
510+
{
511+
String originPartition = ResourceRequest::partitionName(origin.host());
512+
removeResourcesWithOrigin(origin, originPartition);
513+
}
514+
515+
void MemoryCache::removeResourcesWithOrigin(const ClientOrigin& origin)
516+
{
517+
auto cachePartition = origin.topOrigin == origin.clientOrigin ? emptyString() : ResourceRequest::partitionName(origin.topOrigin.host);
518+
removeResourcesWithOrigin(origin.clientOrigin.securityOrigin(), cachePartition);
519+
}
520+
510521
void MemoryCache::removeResourcesWithOrigins(PAL::SessionID sessionID, const HashSet<RefPtr<SecurityOrigin>>& origins)
511522
{
512523
auto* resourceMap = sessionResourceMap(sessionID);

Source/WebCore/loader/cache/MemoryCache.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class ResourceRequest;
4545
class ResourceResponse;
4646
class ScriptExecutionContext;
4747
class SecurityOrigin;
48+
struct ClientOrigin;
4849

4950
// This cache holds subresources used by Web pages: images, scripts, stylesheets, etc.
5051

@@ -151,7 +152,9 @@ class MemoryCache {
151152
bool inLiveDecodedResourcesList(CachedResource& resource) const { return m_liveDecodedResources.contains(&resource); }
152153

153154
typedef HashSet<RefPtr<SecurityOrigin>> SecurityOriginSet;
154-
WEBCORE_EXPORT void removeResourcesWithOrigin(SecurityOrigin&);
155+
WEBCORE_EXPORT void removeResourcesWithOrigin(const SecurityOrigin&);
156+
void removeResourcesWithOrigin(const SecurityOrigin&, const String& cachePartition);
157+
WEBCORE_EXPORT void removeResourcesWithOrigin(const ClientOrigin&);
155158
WEBCORE_EXPORT void removeResourcesWithOrigins(PAL::SessionID, const HashSet<RefPtr<SecurityOrigin>>&);
156159
WEBCORE_EXPORT void getOriginsWithCache(SecurityOriginSet& origins);
157160
WEBCORE_EXPORT HashSet<RefPtr<SecurityOrigin>> originsWithCache(PAL::SessionID) const;

Source/WebCore/platform/network/NetworkStorageSession.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
#include "config.h"
2727
#include "NetworkStorageSession.h"
2828

29+
#include "ClientOrigin.h"
2930
#include "Cookie.h"
3031
#include "CookieJar.h"
3132
#include "HTTPCookieAcceptPolicy.h"
33+
#include "NotImplemented.h"
3234
#include "RuntimeApplicationChecks.h"
3335
#include <wtf/NeverDestroyed.h>
3436
#include <wtf/ProcessPrivilege.h>
@@ -463,4 +465,14 @@ void NetworkStorageSession::deleteCookiesForHostnames(const Vector<String>& cook
463465
deleteCookiesForHostnames(cookieHostNames, IncludeHttpOnlyCookies::Yes, ScriptWrittenCookiesOnly::No, WTFMove(completionHandler));
464466
}
465467

468+
#if !PLATFORM(COCOA)
469+
void NetworkStorageSession::deleteCookies(const ClientOrigin& origin, CompletionHandler<void()>&& completionHandler)
470+
{
471+
// FIXME: Stop ignoring origin.topOrigin.
472+
notImplemented();
473+
474+
deleteCookiesForHostnames(Vector { origin.clientOrigin.host }, WTFMove(completionHandler));
475+
}
476+
#endif
477+
466478
}

Source/WebCore/platform/network/NetworkStorageSession.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class CurlProxySettings;
7777
class NetworkingContext;
7878
class ResourceRequest;
7979

80+
struct ClientOrigin;
8081
struct Cookie;
8182
struct CookieRequestHeaderFieldProxy;
8283
struct SameSiteInfo;
@@ -164,6 +165,7 @@ class NetworkStorageSession : public CanMakeWeakPtr<NetworkStorageSession> {
164165
WEBCORE_EXPORT void deleteCookie(const URL&, const String&, CompletionHandler<void()>&&) const;
165166
WEBCORE_EXPORT void deleteAllCookies(CompletionHandler<void()>&&);
166167
WEBCORE_EXPORT void deleteAllCookiesModifiedSince(WallTime, CompletionHandler<void()>&&);
168+
WEBCORE_EXPORT void deleteCookies(const ClientOrigin&, CompletionHandler<void()>&&);
167169
WEBCORE_EXPORT void deleteCookiesForHostnames(const Vector<String>& cookieHostNames, CompletionHandler<void()>&&);
168170
WEBCORE_EXPORT void deleteCookiesForHostnames(const Vector<String>& cookieHostNames, IncludeHttpOnlyCookies, ScriptWrittenCookiesOnly, CompletionHandler<void()>&&);
169171
WEBCORE_EXPORT Vector<Cookie> getAllCookies();
@@ -243,6 +245,7 @@ class NetworkStorageSession : public CanMakeWeakPtr<NetworkStorageSession> {
243245
RetainPtr<NSArray> cookiesForURL(const URL& firstParty, const SameSiteInfo&, const URL&, std::optional<FrameIdentifier>, std::optional<PageIdentifier>, ApplyTrackingPrevention, ShouldRelaxThirdPartyCookieBlocking) const;
244246
void setHTTPCookiesForURL(CFHTTPCookieStorageRef, NSArray *cookies, NSURL *, NSURL *mainDocumentURL, const SameSiteInfo&) const;
245247
void deleteHTTPCookie(CFHTTPCookieStorageRef, NSHTTPCookie *, CompletionHandler<void()>&&) const;
248+
void deleteCookiesMatching(const Function<bool(NSHTTPCookie *)>& matches, CompletionHandler<void()>&&);
246249
#endif
247250

248251
#if HAVE(COOKIE_CHANGE_LISTENER_API)

Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
#import "config.h"
2727
#import "NetworkStorageSession.h"
2828

29+
#import "ClientOrigin.h"
2930
#import "Cookie.h"
3031
#import "CookieRequestHeaderFieldProxy.h"
3132
#import "CookieStorageObserver.h"
3233
#import "HTTPCookieAcceptPolicyCocoa.h"
34+
#import "ResourceRequest.h"
3335
#import "SameSiteInfo.h"
3436
#import <pal/spi/cf/CFNetworkSPI.h>
3537
#import <wtf/BlockObjCExceptions.h>
@@ -575,50 +577,58 @@ static NSHTTPCookieAcceptPolicy httpCookieAcceptPolicy(CFHTTPCookieStorageRef co
575577
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), makeBlockPtr(WTFMove(work)).get());
576578
}
577579

578-
void NetworkStorageSession::deleteCookiesForHostnames(const Vector<String>& hostnames, IncludeHttpOnlyCookies includeHttpOnlyCookies, ScriptWrittenCookiesOnly scriptWrittenCookiesOnly, CompletionHandler<void()>&& completionHandler)
580+
void NetworkStorageSession::deleteCookiesMatching(const Function<bool(NSHTTPCookie *)>& matches, CompletionHandler<void()>&& completionHandler)
579581
{
580582
ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
581583

582-
auto aggregator = CallbackAggregator::create([completionHandler = WTFMove(completionHandler), cookieStorage = RetainPtr { nsCookieStorage() }] () mutable {
583-
[cookieStorage _saveCookies:makeBlockPtr([completionHandler = WTFMove(completionHandler)] () mutable {
584+
BEGIN_BLOCK_OBJC_EXCEPTIONS
585+
586+
RetainPtr<CFHTTPCookieStorageRef> cookieStorage = this->cookieStorage();
587+
auto nsCookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cookieStorage.get()]);
588+
auto aggregator = CallbackAggregator::create([completionHandler = WTFMove(completionHandler), nsCookieStorage = WTFMove(nsCookieStorage)] () mutable {
589+
[nsCookieStorage _saveCookies:makeBlockPtr([completionHandler = WTFMove(completionHandler)] () mutable {
584590
ensureOnMainThread(WTFMove(completionHandler));
585591
}).get()];
586592
});
587-
588-
BEGIN_BLOCK_OBJC_EXCEPTIONS
589593

590-
RetainPtr<CFHTTPCookieStorageRef> cookieStorage = this->cookieStorage();
591594
RetainPtr<NSArray> cookies = httpCookies(cookieStorage.get());
592595
if (!cookies)
593596
return;
594597

595-
HashMap<String, Vector<RetainPtr<NSHTTPCookie>>> cookiesByDomain;
596598
for (NSHTTPCookie *cookie in cookies.get()) {
597-
if (!cookie.domain || (includeHttpOnlyCookies == IncludeHttpOnlyCookies::No && cookie.isHTTPOnly))
598-
continue;
599+
if (matches(cookie))
600+
deleteHTTPCookie(cookieStorage.get(), cookie, [aggregator] { });
601+
}
599602

603+
END_BLOCK_OBJC_EXCEPTIONS
604+
}
605+
606+
void NetworkStorageSession::deleteCookies(const ClientOrigin& origin, CompletionHandler<void()>&& completionHandler)
607+
{
608+
auto cachePartition = origin.topOrigin == origin.clientOrigin ? emptyString() : ResourceRequest::partitionName(origin.topOrigin.host);
609+
deleteCookiesMatching([domain = origin.clientOrigin.host, &cachePartition](NSHTTPCookie* cookie) {
610+
return String(cookie.domain) == domain && equalIgnoringNullity(String(cookie._storagePartition), cachePartition);
611+
}, WTFMove(completionHandler));
612+
}
613+
614+
void NetworkStorageSession::deleteCookiesForHostnames(const Vector<String>& hostnames, IncludeHttpOnlyCookies includeHttpOnlyCookies, ScriptWrittenCookiesOnly scriptWrittenCookiesOnly, CompletionHandler<void()>&& completionHandler)
615+
{
616+
HashSet<String> hostnamesSet;
617+
for (auto& hostname : hostnames)
618+
hostnamesSet.add(hostname);
619+
620+
deleteCookiesMatching([&](NSHTTPCookie* cookie) {
621+
if (!cookie.domain || (includeHttpOnlyCookies == IncludeHttpOnlyCookies::No && cookie.isHTTPOnly))
622+
return false;
600623
#if ENABLE(JS_COOKIE_CHECKING)
601624
bool setInJS = [[cookie properties] valueForKey:@"SetInJavaScript"];
602625
if (scriptWrittenCookiesOnly == ScriptWrittenCookiesOnly::Yes && !setInJS)
603-
continue;
626+
return false;
604627
#else
605628
UNUSED_PARAM(scriptWrittenCookiesOnly);
606629
#endif
607-
cookiesByDomain.ensure(cookie.domain, [] {
608-
return Vector<RetainPtr<NSHTTPCookie>>();
609-
}).iterator->value.append(cookie);
610-
}
611-
612-
for (const auto& hostname : hostnames) {
613-
auto it = cookiesByDomain.find(hostname);
614-
if (it == cookiesByDomain.end())
615-
continue;
616-
617-
for (auto& cookie : it->value)
618-
deleteHTTPCookie(cookieStorage.get(), cookie.get(), [aggregator] { });
619-
}
620-
621-
END_BLOCK_OBJC_EXCEPTIONS
630+
return hostnamesSet.contains(String(cookie.domain));
631+
}, WTFMove(completionHandler));
622632
}
623633

624634
void NetworkStorageSession::deleteAllCookiesModifiedSince(WallTime timePoint, CompletionHandler<void()>&& completionHandler)

Source/WebCore/workers/service/server/SWServer.cpp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -313,28 +313,42 @@ void SWServer::endSuspension()
313313
}
314314

315315
void SWServer::clear(const SecurityOriginData& securityOrigin, CompletionHandler<void()>&& completionHandler)
316+
{
317+
clearInternal([securityOrigin](auto& key) {
318+
return key.relatesToOrigin(securityOrigin);
319+
}, WTFMove(completionHandler));
320+
}
321+
322+
void SWServer::clear(const ClientOrigin& origin, CompletionHandler<void()>&& completionHandler)
323+
{
324+
clearInternal([origin](auto& key) {
325+
return key.topOrigin() == origin.topOrigin && origin.clientOrigin == SecurityOriginData::fromURL(key.scope());
326+
}, WTFMove(completionHandler));
327+
}
328+
329+
void SWServer::clearInternal(Function<bool(const ServiceWorkerRegistrationKey&)>&& matches, CompletionHandler<void()>&& completionHandler)
316330
{
317331
if (!m_importCompleted) {
318-
m_clearCompletionCallbacks.append([this, securityOrigin, completionHandler = WTFMove(completionHandler)] () mutable {
332+
m_clearCompletionCallbacks.append([this, matches = WTFMove(matches), completionHandler = WTFMove(completionHandler)] () mutable {
319333
ASSERT(m_importCompleted);
320-
clear(securityOrigin, WTFMove(completionHandler));
334+
clearInternal(WTFMove(matches), WTFMove(completionHandler));
321335
});
322336
return;
323337
}
324338

325339
m_jobQueues.removeIf([&](auto& keyAndValue) {
326-
return keyAndValue.key.relatesToOrigin(securityOrigin);
340+
return matches(keyAndValue.key);
327341
});
328342

329343
Vector<SWServerRegistration*> registrationsToRemove;
330344
for (auto& registration : m_registrations.values()) {
331-
if (registration->key().relatesToOrigin(securityOrigin))
345+
if (matches(registration->key()))
332346
registrationsToRemove.append(registration.get());
333347
}
334348

335349
for (auto& contextDatas : m_pendingContextDatas.values()) {
336350
contextDatas.removeAllMatching([&](auto& contextData) {
337-
return contextData.registration.key.relatesToOrigin(securityOrigin);
351+
return matches(contextData.registration.key);
338352
4285 });
339353
}
340354

0 commit comments

Comments
 (0)
0