Commit 43949bf7 authored by Istiaque Ahmed's avatar Istiaque Ahmed Committed by Commit Bot

[Extensions] Fix couple of ProcessManager UaF issues

Worker can legitimately fail to start, this CL clears worker's
PendingTasks when that happens.

In addition to this, this CL makes ServiceWorkerTaskQueue
factory dependent on ProcessManager factory as pending tasks
can call out to ProcessManager
(courtesy of https://crbug.com/1019161#c16) upon
ServiceWorkerTaskQueue's destruction.

This CL adds a test for this ensuring a worker's pending_tasks_
is cleared when start worker failure is seen. The test rejects a
service worker's install event to trigger the failure.

Bug: 1019161
Test: See bug description for repro steps
Change-Id: I384ec0d2830f07fb3b50632ee806e77fd33b7dcb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2103306
Commit-Queue: Istiaque Ahmed <lazyboy@chromium.org>
Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#751925}
parent f47d4c2f
...@@ -520,6 +520,49 @@ class ServiceWorkerRegistrationAtStartupTest ...@@ -520,6 +520,49 @@ class ServiceWorkerRegistrationAtStartupTest
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRegistrationAtStartupTest); DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRegistrationAtStartupTest);
}; };
// Observes ServiceWorkerTaskQueue::DidStartWorkerFail.
class ServiceWorkerStartFailureObserver
: public ServiceWorkerTaskQueue::TestObserver {
public:
explicit ServiceWorkerStartFailureObserver(const ExtensionId& extension_id)
: extension_id_(extension_id) {
ServiceWorkerTaskQueue::SetObserverForTest(this);
}
~ServiceWorkerStartFailureObserver() override {
ServiceWorkerTaskQueue::SetObserverForTest(nullptr);
}
ServiceWorkerStartFailureObserver(const ServiceWorkerStartFailureObserver&) =
delete;
ServiceWorkerStartFailureObserver& operator=(
const ServiceWorkerStartFailureObserver&) = delete;
size_t WaitForDidStartWorkerFailAndGetTaskCount() {
if (pending_tasks_count_at_worker_failure_)
return *pending_tasks_count_at_worker_failure_;
run_loop_.Run();
return *pending_tasks_count_at_worker_failure_;
}
// ServiceWorkerTaskQueue::TestObserver:
void DidStartWorkerFail(const ExtensionId& extension_id,
size_t num_pending_tasks) override {
if (extension_id == extension_id_) {
pending_tasks_count_at_worker_failure_ = num_pending_tasks;
run_loop_.Quit();
}
}
private:
// Holds number of pending tasks for worker at the time DidStartWorkerFail is
// observed.
base::Optional<size_t> pending_tasks_count_at_worker_failure_;
ExtensionId extension_id_;
base::RunLoop run_loop_;
};
// Test extension id at // Test extension id at
// api_test/service_worker/worker_based_background/registration_at_startup/. // api_test/service_worker/worker_based_background/registration_at_startup/.
const char ServiceWorkerRegistrationAtStartupTest::kExtensionId[] = const char ServiceWorkerRegistrationAtStartupTest::kExtensionId[] =
...@@ -1581,6 +1624,64 @@ class TestRegistrationObserver : public content::ServiceWorkerContextObserver { ...@@ -1581,6 +1624,64 @@ class TestRegistrationObserver : public content::ServiceWorkerContextObserver {
content::ServiceWorkerContext* context_; content::ServiceWorkerContext* context_;
}; };
// Observer for an extension service worker to start and stop.
class TestWorkerObserver : public content::ServiceWorkerContextObserver {
public:
TestWorkerObserver(content::ServiceWorkerContext* context,
const ExtensionId& extension_id)
: context_(context),
extension_url_(Extension::GetBaseURLFromExtensionId(extension_id)) {
context_->AddObserver(this);
}
~TestWorkerObserver() override {
if (context_) {
context_->RemoveObserver(this);
}
}
TestWorkerObserver(const TestWorkerObserver&) = delete;
TestWorkerObserver& operator=(const TestWorkerObserver&) = delete;
void WaitForWorkerStart() {
if (running_version_id_.has_value())
return;
started_run_loop_.Run();
}
void WaitForWorkerStop() {
DCHECK(running_version_id_.has_value()) << "Worker hasn't started";
stopped_run_loop_.Run();
}
private:
// ServiceWorkerContextObserver:
void OnVersionStartedRunning(
int64_t version_id,
const content::ServiceWorkerRunningInfo& running_info) override {
if (running_info.scope != extension_url_)
return;
running_version_id_ = version_id;
started_run_loop_.Quit();
}
void OnVersionStoppedRunning(int64_t version_id) override {
if (running_version_id_ == version_id)
stopped_run_loop_.Quit();
}
void OnDestruct(content::ServiceWorkerContext* context) override {
context_->RemoveObserver(this);
context_ = nullptr;
}
base::RunLoop started_run_loop_;
base::RunLoop stopped_run_loop_;
// Holds version id of an extension worker once OnVersionStartedRunning is
// observed.
base::Optional<int64_t> running_version_id_;
content::ServiceWorkerContext* context_ = nullptr;
GURL extension_url_;
};
IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
EventsToStoppedWorker) { EventsToStoppedWorker) {
content::StoragePartition* storage_partition = content::StoragePartition* storage_partition =
...@@ -1833,6 +1934,79 @@ IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, ...@@ -1833,6 +1934,79 @@ IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
EXPECT_EQ(1, observer.GetCompletedCount(extension->url())); EXPECT_EQ(1, observer.GetCompletedCount(extension->url()));
} }
// Tests that a worker that failed to start due to 'install' error, clears its
// PendingTasks correctly. Also tests that subsequent tasks are properly
// cleared.
// Regression test for https://crbug.com/1019161.
IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
WorkerStartFailureClearsPendingTasks) {
content::StoragePartition* storage_partition =
content::BrowserContext::GetDefaultStoragePartition(browser()->profile());
content::ServiceWorkerContext* context =
storage_partition->GetServiceWorkerContext();
const ExtensionId kTestExtensionId("iegclhlplifhodhkoafiokenjoapiobj");
// Set up an observer to wait for worker to start and then stop.
TestWorkerObserver observer(context, kTestExtensionId);
TestExtensionDir test_dir;
// Key for extension id |kTestExtensionId|.
constexpr const char kKey[] =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjzv7dI7Ygyh67VHE1DdidudpYf8P"
"Ffv8iucWvzO+3xpF/Dm5xNo7aQhPNiEaNfHwJQ7lsp4gc+C+4bbaVewBFspTruoSJhZc5uEf"
"qxwovJwN+v1/SUFXTXQmQBv6gs0qZB4gBbl4caNQBlqrFwAMNisnu1V6UROna8rOJQ90D7Nv"
"7TCwoVPKBfVshpFjdDOTeBg4iLctO3S/06QYqaTDrwVceSyHkVkvzBY6tc6mnYX0RZu78J9i"
"L8bdqwfllOhs69cqoHHgrLdI6JdOyiuh6pBP6vxMlzSKWJ3YTNjaQTPwfOYaLMuzdl0v+Ydz"
"afIzV9zwe4Xiskk+5JNGt8b2rQIDAQAB";
test_dir.WriteManifest(base::StringPrintf(
R"({
"name": "Test Extension",
"manifest_version": 2,
"version": "0.1",
"key": "%s",
"permissions": ["tabs"],
"background": {"service_worker": "script.js"}
})",
kKey));
constexpr char kScript[] =
R"(self.oninstall = function(event) {
event.waitUntil(Promise.reject(new Error('foo')));
};)";
test_dir.WriteFile(FILE_PATH_LITERAL("script.js"), kScript);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
ASSERT_EQ(kTestExtensionId, extension->id());
LazyContextId context_id(browser()->profile(), extension->id(),
extension->url());
ServiceWorkerStartFailureObserver worker_start_failure_observer(
extension->id());
// Let the worker start so it rejects 'install' event. This causes the worker
// to stop.
observer.WaitForWorkerStart();
observer.WaitForWorkerStop();
ServiceWorkerTaskQueue* service_worker_task_queue =
ServiceWorkerTaskQueue::Get(browser()->profile());
// Adding a pending task to ServiceWorkerTaskQueue will try to start the
// worker that failed during installation before. This enables us to ensure
// that this pending task is cleared on failure.
service_worker_task_queue->AddPendingTask(context_id, base::DoNothing());
// Since the worker rejects installation, it will fail to start now. Ensure
// that the queue sees pending tasks while the error is observed.
EXPECT_GT(
worker_start_failure_observer.WaitForDidStartWorkerFailAndGetTaskCount(),
0u);
// Ensure DidStartWorkerFail finished clearing tasks.
base::RunLoop().RunUntilIdle();
// And the task count will be reset to zero afterwards.
EXPECT_EQ(0u,
service_worker_task_queue->GetNumPendingTasksForTest(context_id));
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
ProcessManagerRegistrationOnShutdown) { ProcessManagerRegistrationOnShutdown) {
// Note that StopServiceWorkerForScope call below expects the worker to be // Note that StopServiceWorkerForScope call below expects the worker to be
......
...@@ -225,8 +225,17 @@ void ServiceWorkerTaskQueue::DidStartWorkerFail( ...@@ -225,8 +225,17 @@ void ServiceWorkerTaskQueue::DidStartWorkerFail(
return; return;
} }
// TODO(lazyboy): Handle failure cases. WorkerState* worker_state = GetWorkerState(context_id);
DCHECK(false) << "DidStartWorkerFail: " << context_id.first.extension_id(); DCHECK(worker_state);
if (g_test_observer) {
g_test_observer->DidStartWorkerFail(context_id.first.extension_id(),
worker_state->pending_tasks_.size());
}
worker_state->pending_tasks_.clear();
// TODO(https://crbug/1062936): Needs more thought: extension would be in
// perma-broken state after this as the registration wouldn't be stored if
// this happens.
LOG(ERROR) << "DidStartWorkerFail " << context_id.first.extension_id();
} }
void ServiceWorkerTaskQueue::DidInitializeServiceWorkerContext( void ServiceWorkerTaskQueue::DidInitializeServiceWorkerContext(
...@@ -585,6 +594,16 @@ base::Optional<ActivationSequence> ServiceWorkerTaskQueue::GetCurrentSequence( ...@@ -585,6 +594,16 @@ base::Optional<ActivationSequence> ServiceWorkerTaskQueue::GetCurrentSequence(
return iter->second; return iter->second;
} }
size_t ServiceWorkerTaskQueue::GetNumPendingTasksForTest(
const LazyContextId& lazy_context_id) {
auto current_sequence = GetCurrentSequence(lazy_context_id.extension_id());
if (!current_sequence)
return 0u;
const SequencedContextId context_id(lazy_context_id, *current_sequence);
WorkerState* worker_state = GetWorkerState(context_id);
return worker_state ? worker_state->pending_tasks_.size() : 0u;
}
ServiceWorkerTaskQueue::WorkerState* ServiceWorkerTaskQueue::GetWorkerState( ServiceWorkerTaskQueue::WorkerState* ServiceWorkerTaskQueue::GetWorkerState(
const SequencedContextId& context_id) { const SequencedContextId& context_id) {
auto worker_iter = worker_state_map_.find(context_id); auto worker_iter = worker_state_map_.find(context_id);
......
...@@ -130,7 +130,9 @@ class ServiceWorkerTaskQueue : public KeyedService, ...@@ -130,7 +130,9 @@ class ServiceWorkerTaskQueue : public KeyedService,
// |will_register_service_worker| is true if a Service Worker will be // |will_register_service_worker| is true if a Service Worker will be
// registered. // registered.
virtual void OnActivateExtension(const ExtensionId& extension_id, virtual void OnActivateExtension(const ExtensionId& extension_id,
bool will_register_service_worker) = 0; bool will_register_service_worker) {}
virtual void DidStartWorkerFail(const ExtensionId& extension_id,
size_t num_pending_tasks) {}
private: private:
DISALLOW_COPY_AND_ASSIGN(TestObserver); DISALLOW_COPY_AND_ASSIGN(TestObserver);
...@@ -138,6 +140,8 @@ class ServiceWorkerTaskQueue : public KeyedService, ...@@ -138,6 +140,8 @@ class ServiceWorkerTaskQueue : public KeyedService,
static void SetObserverForTest(TestObserver* observer); static void SetObserverForTest(TestObserver* observer);
size_t GetNumPendingTasksForTest(const LazyContextId& lazy_context_id);
private: private:
using SequencedContextId = std::pair<LazyContextId, ActivationSequence>; using SequencedContextId = std::pair<LazyContextId, ActivationSequence>;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "extensions/browser/extension_registry_factory.h" #include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/process_manager_factory.h"
#include "extensions/browser/service_worker_task_queue.h" #include "extensions/browser/service_worker_task_queue.h"
using content::BrowserContext; using content::BrowserContext;
...@@ -28,6 +29,7 @@ ServiceWorkerTaskQueueFactory::ServiceWorkerTaskQueueFactory() ...@@ -28,6 +29,7 @@ ServiceWorkerTaskQueueFactory::ServiceWorkerTaskQueueFactory()
"ServiceWorkerTaskQueue", "ServiceWorkerTaskQueue",
BrowserContextDependencyManager::GetInstance()) { BrowserContextDependencyManager::GetInstance()) {
DependsOn(ExtensionRegistryFactory::GetInstance()); DependsOn(ExtensionRegistryFactory::GetInstance());
DependsOn(ProcessManagerFactory::GetInstance());
} }
ServiceWorkerTaskQueueFactory::~ServiceWorkerTaskQueueFactory() {} ServiceWorkerTaskQueueFactory::~ServiceWorkerTaskQueueFactory() {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment