Skip to content

Commit

Permalink
Remote worker processes may not obey the lockdown mode setting
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=268183
rdar://121617300

Reviewed by Youenn Fablet.

Make sure we carry over the requesting process' lockdown mode state to the
newly created process when we decide to launch a remote worker process.

Also make sure that the settings that are meant to be disabled in lockdown
mode also get disabled in the remote worker contexts, not just in page/window
contexts.

* Source/WebKit/UIProcess/API/Cocoa/WKProcessPool.mm:
(-[WKProcessPool _isJITDisabledInAllServiceWorkerProcesses:]):
* Source/WebKit/UIProcess/API/Cocoa/WKProcessPoolPrivate.h:
* Source/WebKit/UIProcess/WebProcessPool.cpp:
(WebKit::WebProcessPool::establishRemoteWorkerContextConnectionToNetworkProcess):
(WebKit::WebProcessPool::isJITDisabledInAllServiceWorkerProcesses const):
* Source/WebKit/UIProcess/WebProcessPool.h:
* Source/WebKit/UIProcess/WebProcessProxy.cpp:
(WebKit::WebProcessProxy::createForRemoteWorkers):
* Source/WebKit/UIProcess/WebProcessProxy.h:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm:

Originally-landed-as: 272448.386@safari-7618-branch (dd435ec). rdar://124554815
Canonical link: https://commits.webkit.org/276660@main
  • Loading branch information
cdumez authored and JonWBedard committed Mar 25, 2024
1 parent 4f87037 commit 1c69db1
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 12 deletions.
7 changes: 7 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/WKProcessPool.mm
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,13 @@ - (size_t)_serviceWorkerProcessCount
return _processPool->serviceWorkerProxiesCount();
}

- (void)_isJITDisabledInAllRemoteWorkerProcesses:(void(^)(BOOL))completionHandler
{
_processPool->isJITDisabledInAllRemoteWorkerProcesses([completionHandler = makeBlockPtr(completionHandler)] (bool result) {
completionHandler(result);
});
}

+ (void)_forceGameControllerFramework
{
#if ENABLE(GAMEPAD)
Expand Down
1 change: 1 addition & 0 deletions Source/WebKit/UIProcess/API/Cocoa/WKProcessPoolPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))
- (size_t)_webProcessCountIgnoringPrewarmedAndCached WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
- (size_t)_pluginProcessCount WK_API_AVAILABLE(macos(10.13.4), ios(11.3));
- (size_t)_serviceWorkerProcessCount WK_API_AVAILABLE(macos(10.14), ios(12.0));
- (void)_isJITDisabledInAllRemoteWorkerProcesses:(void(^)(BOOL))completionHandler;
- (void)_makeNextWebProcessLaunchFailForTesting WK_API_AVAILABLE(macos(10.14), ios(12.0));
- (NSUInteger)_maximumSuspendedPageCount WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
- (NSUInteger)_processCacheCapacity WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
Expand Down
36 changes: 35 additions & 1 deletion Source/WebKit/UIProcess/WebProcessPool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ void WebProcessPool::establishRemoteWorkerContextConnectionToNetworkProcess(Remo
static NeverDestroyed<Ref<WebProcessPool>> remoteWorkerProcessPool(WebProcessPool::create(API::ProcessPoolConfiguration::create().get()));

RefPtr requestingProcess = requestingProcessIdentifier ? WebProcessProxy::processForIdentifier(*requestingProcessIdentifier) : nullptr;
auto lockdownMode = requestingProcess ? requestingProcess->lockdownMode() : (lockdownModeEnabledBySystem() ? WebProcessProxy::LockdownMode::Enabled : WebProcessProxy::LockdownMode::Disabled);
Ref processPool = requestingProcess ? requestingProcess->processPool() : processPools()[0].get();

RefPtr<WebProcessProxy> remoteWorkerProcessProxy;
Expand Down Expand Up @@ -671,6 +672,8 @@ void WebProcessPool::establishRemoteWorkerContextConnectionToNetworkProcess(Remo
continue;
if (!process->isMatchingRegistrableDomain(registrableDomain))
continue;
if (process->lockdownMode() != lockdownMode)
continue;

useProcessForRemoteWorkers(process);

Expand All @@ -694,7 +697,7 @@ void WebProcessPool::establishRemoteWorkerContextConnectionToNetworkProcess(Remo
ASSERT(preferencesStore);

if (!remoteWorkerProcessProxy) {
Ref newProcessProxy = WebProcessProxy::createForRemoteWorkers(workerType, processPool, RegistrableDomain { registrableDomain }, *websiteDataStore);
Ref newProcessProxy = WebProcessProxy::createForRemoteWorkers(workerType, processPool, RegistrableDomain { registrableDomain }, *websiteDataStore, lockdownMode);
remoteWorkerProcessProxy = newProcessProxy.copyRef();

WEBPROCESSPOOL_RELEASE_LOG_STATIC(ServiceWorker, "establishRemoteWorkerContextConnectionToNetworkProcess creating a new service worker process (process=%p, workerType=%" PUBLIC_LOG_STRING ", PID=%d)", remoteWorkerProcessProxy.get(), workerType == RemoteWorkerType::ServiceWorker ? "service" : "shared", remoteWorkerProcessProxy->processID());
Expand Down Expand Up @@ -2405,6 +2408,37 @@ size_t WebProcessPool::serviceWorkerProxiesCount() const
return count;
}

void WebProcessPool::isJITDisabledInAllRemoteWorkerProcesses(CompletionHandler<void(bool)>&& completionHandler) const
{
class JITDisabledCallbackAggregator : public RefCounted<JITDisabledCallbackAggregator> {
public:
static auto create(CompletionHandler<void(bool)>&& callback) { return adoptRef(*new JITDisabledCallbackAggregator(WTFMove(callback))); }

~JITDisabledCallbackAggregator()
{
if (m_callback)
m_callback(m_isJITDisabled);
}

void setJITEnabled(bool isJITEnabled) { m_isJITDisabled &= !isJITEnabled; }

private:
explicit JITDisabledCallbackAggregator(CompletionHandler<void(bool)>&& callback)
: m_callback(WTFMove(callback))
{ }

CompletionHandler<void(bool)> m_callback;
bool m_isJITDisabled { true };
};

Ref callbackAggregator = JITDisabledCallbackAggregator::create(WTFMove(completionHandler));
remoteWorkerProcesses().forEach([&](auto& process) {
process.sendWithAsyncReply(Messages::WebProcess::IsJITEnabled(), [callbackAggregator](bool isJITEnabled) {
callbackAggregator->setJITEnabled(isJITEnabled);
}, 0);
});
}

bool WebProcessPool::hasServiceWorkerForegroundActivityForTesting() const
{
return WTF::anyOf(remoteWorkerProcesses(), [](auto& process) {
Expand Down
1 change: 1 addition & 0 deletions Source/WebKit/UIProcess/WebProcessPool.h
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ class WebProcessPool final
bool isServiceWorkerPageID(WebPageProxyIdentifier) const;

size_t serviceWorkerProxiesCount() const;
void isJITDisabledInAllRemoteWorkerProcesses(CompletionHandler<void(bool)>&&) const;
bool hasServiceWorkerForegroundActivityForTesting() const;
bool hasServiceWorkerBackgroundActivityForTesting() const;
void serviceWorkerProcessCrashed(WebProcessProxy&, ProcessTerminationReason);
Expand Down
4 changes: 2 additions & 2 deletions Source/WebKit/UIProcess/WebProcessProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,9 @@ Ref<WebProcessProxy> WebProcessProxy::create(WebProcessPool& processPool, Websit
return proxy;
}

Ref<WebProcessProxy> WebProcessProxy::createForRemoteWorkers(RemoteWorkerType workerType, WebProcessPool& processPool, RegistrableDomain&& registrableDomain, WebsiteDataStore& websiteDataStore)
Ref<WebProcessProxy> WebProcessProxy::createForRemoteWorkers(RemoteWorkerType workerType, WebProcessPool& processPool, RegistrableDomain&& registrableDomain, WebsiteDataStore& websiteDataStore, LockdownMode lockdownMode)
{
Ref proxy = adoptRef(*new WebProcessProxy(processPool, &websiteDataStore, IsPrewarmed::No, CrossOriginMode::Shared, LockdownMode::Disabled));
Ref proxy = adoptRef(*new WebProcessProxy(processPool, &websiteDataStore, IsPrewarmed::No, CrossOriginMode::Shared, lockdownMode));
proxy->m_registrableDomain = WTFMove(registrableDomain);
proxy->enableRemoteWorkers(workerType, processPool.userContentControllerIdentifierForRemoteWorkers());
proxy->connect();
Expand Down
2 changes: 1 addition & 1 deletion Source/WebKit/UIProcess/WebProcessProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class WebProcessProxy : public AuxiliaryProcessProxy {
enum class LockdownMode : bool { Disabled, Enabled };

static Ref<WebProcessProxy> create(WebProcessPool&, WebsiteDataStore*, LockdownMode, IsPrewarmed, WebCore::CrossOriginMode = WebCore::CrossOriginMode::Shared, ShouldLaunchProcess = ShouldLaunchProcess::Yes);
static Ref<WebProcessProxy> createForRemoteWorkers(RemoteWorkerType, WebProcessPool&, WebCore::RegistrableDomain&&, WebsiteDataStore&);
static Ref<WebProcessProxy> createForRemoteWorkers(RemoteWorkerType, WebProcessPool&, WebCore::RegistrableDomain&&, WebsiteDataStore&, LockdownMode);

~WebProcessProxy();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ void WebSWContextManagerConnection::installServiceWorker(ServiceWorkerContextDat
WebPage::updateSettingsGenerated(*m_preferencesStore, page->settings());
page->settings().setStorageBlockingPolicy(static_cast<StorageBlockingPolicy>(m_preferencesStore->getUInt32ValueForKey(WebPreferencesKey::storageBlockingPolicyKey())));
}
if (WebProcess::singleton().isLockdownModeEnabled())
WebPage::adjustSettingsForLockdownMode(page->settings(), m_preferencesStore ? &m_preferencesStore.value() : nullptr);

page->setupForRemoteWorker(contextData.scriptURL, contextData.registration.key.topOrigin(), contextData.referrerPolicy);
#if ENABLE(REMOTE_INSPECTOR)
page->setInspectable(inspectable == ServiceWorkerIsInspectable::Yes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ void WebSharedWorkerContextManagerConnection::launchSharedWorker(WebCore::Client
WebPage::updateSettingsGenerated(*m_preferencesStore, page->settings());
page->settings().setStorageBlockingPolicy(static_cast<WebCore::StorageBlockingPolicy>(m_preferencesStore->getUInt32ValueForKey(WebPreferencesKey::storageBlockingPolicyKey())));
}
if (WebProcess::singleton().isLockdownModeEnabled())
WebPage::adjustSettingsForLockdownMode(page->settings(), m_preferencesStore ? &m_preferencesStore.value() : nullptr);

if (!initializationData.userAgent.isEmpty())
initializationData.userAgent = m_userAgent;
Expand Down
18 changes: 10 additions & 8 deletions Source/WebKit/WebProcess/WebPage/WebPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4557,7 +4557,7 @@ bool WebPage::isParentProcessAWebBrowser() const
return false;
}

static void adjustSettingsForLockdownMode(Settings& settings, const WebPreferencesStore& store)
void WebPage::adjustSettingsForLockdownMode(Settings& settings, const WebPreferencesStore* store)
{
// Disable unstable Experimental settings, even if the user enabled them for local use.
settings.disableUnstableFeaturesForModernWebKit();
Expand Down Expand Up @@ -4626,16 +4626,18 @@ static void adjustSettingsForLockdownMode(Settings& settings, const WebPreferenc
settings.setWebLocksAPIEnabled(false);
settings.setCacheAPIEnabled(false);

settings.setAllowedMediaContainerTypes(store.getStringValueForKey(WebPreferencesKey::mediaContainerTypesAllowedInLockdownModeKey()));
settings.setAllowedMediaCodecTypes(store.getStringValueForKey(WebPreferencesKey::mediaCodecTypesAllowedInLockdownModeKey()));
settings.setAllowedMediaVideoCodecIDs(store.getStringValueForKey(WebPreferencesKey::mediaVideoCodecIDsAllowedInLockdownModeKey()));
settings.setAllowedMediaAudioCodecIDs(store.getStringValueForKey(WebPreferencesKey::mediaAudioCodecIDsAllowedInLockdownModeKey()));
settings.setAllowedMediaCaptionFormatTypes(store.getStringValueForKey(WebPreferencesKey::mediaCaptionFormatTypesAllowedInLockdownModeKey()));

// FIXME: This seems like an odd place to put logic for setting global state in CoreGraphics.
#if HAVE(LOCKDOWN_MODE_PDF_ADDITIONS)
CGEnterLockdownModeForPDF();
#endif

if (store) {
settings.setAllowedMediaContainerTypes(store->getStringValueForKey(WebPreferencesKey::mediaContainerTypesAllowedInLockdownModeKey()));
settings.setAllowedMediaCodecTypes(store->getStringValueForKey(WebPreferencesKey::mediaCodecTypesAllowedInLockdownModeKey()));
settings.setAllowedMediaVideoCodecIDs(store->getStringValueForKey(WebPreferencesKey::mediaVideoCodecIDsAllowedInLockdownModeKey()));
settings.setAllowedMediaAudioCodecIDs(store->getStringValueForKey(WebPreferencesKey::mediaAudioCodecIDsAllowedInLockdownModeKey()));
settings.setAllowedMediaCaptionFormatTypes(store->getStringValueForKey(WebPreferencesKey::mediaCaptionFormatTypesAllowedInLockdownModeKey()));
}
}

void WebPage::updatePreferences(const WebPreferencesStore& store)
Expand Down Expand Up @@ -4769,7 +4771,7 @@ void WebPage::updatePreferences(const WebPreferencesStore& store)
// FIXME: This should be automated by adding a new field in WebPreferences*.yaml
// that indicates override state for Lockdown mode. https://webkit.org/b/233100.
if (WebProcess::singleton().isLockdownModeEnabled())
adjustSettingsForLockdownMode(settings, store);
adjustSettingsForLockdownMode(settings, &store);

#if ENABLE(ARKIT_INLINE_PREVIEW)
m_useARKitForModel = store.getBoolValueForKey(WebPreferencesKey::useARKitForModelKey());
Expand Down
2 changes: 2 additions & 0 deletions Source/WebKit/WebProcess/WebPage/WebPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,8 @@ class WebPage : public API::ObjectImpl<API::Object::Type::BundlePage>, public IP
bool userIsInteracting() const { return m_userIsInteracting; }
void setUserIsInteracting(bool userIsInteracting) { m_userIsInteracting = userIsInteracting; }

static void adjustSettingsForLockdownMode(WebCore::Settings&, const WebPreferencesStore*);

#if PLATFORM(IOS_FAMILY)
// This excludes layout overflow, includes borders.
static WebCore::IntRect rootViewBounds(const WebCore::Node&);
Expand Down
101 changes: 101 additions & 0 deletions Tools/TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ @interface SWMessageHandlerForRestoreFromDiskTest : NSObject <WKScriptMessageHan
NSString *_expectedMessage;
}
- (instancetype)initWithExpectedMessage:(NSString *)expectedMessage;
- (void)resetExpectedMessage:(NSString *)expectedMessage;
@end

@interface SWMessageHandlerWithExpectedMessage : NSObject <WKScriptMessageHandler>
Expand All @@ -123,6 +124,11 @@ - (instancetype)initWithExpectedMessage:(NSString *)expectedMessage
return self;
}

- (void)resetExpectedMessage:(NSString *)expectedMessage
{
_expectedMessage = expectedMessage;
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
EXPECT_WK_STREQ(message.body, _expectedMessage);
Expand Down Expand Up @@ -233,6 +239,18 @@ function log(msg)

)SWRESOURCE"_s;

static constexpr auto scriptWithEvalBytes = R"SWRESOURCE(

self.addEventListener("message", (event) => {
if (event.data == "Hello from the web page") {
event.source.postMessage("ServiceWorker received: " + event.data);
return;
}
event.source.postMessage("Evaluation result: " + eval(event.data));
});

)SWRESOURCE"_s;

static constexpr auto mainForFetchTestBytes = R"SWRESOURCE(
<html>
<body>
Expand Down Expand Up @@ -1739,6 +1757,89 @@ static size_t launchServiceWorkerProcess(bool useSeparateServiceWorkerProcess, b
EXPECT_EQ(2u, launchServiceWorkerProcess(useSeparateServiceWorkerProcess, firstLoadAboutBlank));
}

TEST(ServiceWorkers, LockdownModeInServiceWorkerProcess)
{
// Turn on lockdown mode globally.
[WKProcessPool _setCaptivePortalModeEnabledGloballyForTesting:YES];
[WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
TestWebKitAPI::Util::spinRunLoop();

// Start with a clean slate data store.
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;

RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);

RetainPtr messageHandler = adoptNS([[SWMessageHandlerForRestoreFromDiskTest alloc] initWithExpectedMessage:@"Message from worker: ServiceWorker received: Hello from the web page"]);
[[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];

TestWebKitAPI::HTTPServer server({
{ "/"_s, { mainBytes } },
{ "/sw.js"_s, { {{ "Content-Type"_s, "application/javascript"_s }}, scriptWithEvalBytes } }
});

RetainPtr processPool = retainPtr(configuration.get().processPool);

// Make sure that the service worker launches in its own process.
[processPool _setUseSeparateServiceWorkerProcess:YES];

RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);

RetainPtr navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
[navigationDelegate setDidFinishNavigation:^(WKWebView *, WKNavigation *) {
didFinishNavigationBoolean = true;
}];
[webView setNavigationDelegate:navigationDelegate.get()];
[webView loadRequest:server.request()];

TestWebKitAPI::Util::run(&done);
done = false;

EXPECT_TRUE(waitUntilEvaluatesToTrue([&] { return [processPool _serviceWorkerProcessCount] == 1; }));

// Check that JIT is disabled in the service worker process.
done = false;
[processPool _isJITDisabledInAllRemoteWorkerProcesses:^(BOOL isJITDisabled) {
EXPECT_TRUE(isJITDisabled);
done = true;
}];
TestWebKitAPI::Util::run(&done);

auto runJSCheck = [&](const String& jsToEvalInWorker) {
bool finishedRunningScript = false;
done = false;
String js = makeString("worker.postMessage('", jsToEvalInWorker,"');");
[webView evaluateJavaScript:js completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
finishedRunningScript = true;
}];
TestWebKitAPI::Util::run(&finishedRunningScript);
TestWebKitAPI::Util::run(&done);
};

[messageHandler resetExpectedMessage:@"Message from worker: Evaluation result: true"];
runJSCheck("!!self.URL"_s);

// Check individual settings that are meant to be disabled in lockdown mode.
[messageHandler resetExpectedMessage:@"Message from worker: Evaluation result: false"];
runJSCheck("!!self.WebGL2RenderingContext"_s);
runJSCheck("!!self.FileSystemHandle"_s); // File System Access.
#if ENABLE(NOTIFICATIONS)
runJSCheck("!!self.Notification"_s); // Notification API.
#endif
runJSCheck("!!self.Cache"_s); // Cache API.
runJSCheck("!!self.CacheStorage"_s); // Cache API.
runJSCheck("!!self.FileReader"_s); // FileReader API.
runJSCheck("!!self.FileSystemFileHandle"_s); // File System Access API.
runJSCheck("!!self.PushManager"_s); // Push API.
runJSCheck("!!self.PushSubscription"_s); // Push API.
runJSCheck("!!self.PushSubscriptionOptions"_s); // Push API.
runJSCheck("!!self.LockManager"_s); // WebLockManager API.
}

enum class UseSeparateServiceWorkerProcess : bool { No, Yes };
void testSuspendServiceWorkerProcessBasedOnClientProcesses(UseSeparateServiceWorkerProcess useSeparateServiceWorkerProcess)
{
Expand Down

0 comments on commit 1c69db1

Please sign in to comment.