Commit 9cca4806 authored by Dominic Mazzoni's avatar Dominic Mazzoni Committed by Commit Bot

Fix UAF in TtsPlatformImpl if a BrowserContext is deleted.

Bug: 1081350
Change-Id: I2b1824abefbd7fc3e8ce1c0cb433896161bab4e5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2211123Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Reviewed-by: default avatarJohn Abd-El-Malek <jam@chromium.org>
Commit-Queue: Dominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#771222}
parent 08eab1dd
...@@ -30,16 +30,17 @@ ...@@ -30,16 +30,17 @@
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AnyNumber; using ::testing::AnyNumber;
using ::testing::DoAll; using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::InSequence; using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs; using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
using ::testing::Return; using ::testing::Return;
using ::testing::SaveArg; using ::testing::SaveArg;
using ::testing::SetArgPointee; using ::testing::SetArgPointee;
using ::testing::StrictMock; using ::testing::StrictMock;
using ::testing::_;
namespace { namespace {
int g_saved_utterance_id; int g_saved_utterance_id;
...@@ -284,7 +285,13 @@ class TtsApiTest : public ExtensionApiTest { ...@@ -284,7 +285,13 @@ class TtsApiTest : public ExtensionApiTest {
return false; return false;
} }
void IgnoreAnyAdditionalCallsToMockPlatformImpl() {
content::TtsController::GetInstance()->SetTtsPlatform(
&nice_mock_platform_impl_);
}
StrictMock<MockTtsPlatformImpl> mock_platform_impl_; StrictMock<MockTtsPlatformImpl> mock_platform_impl_;
NiceMock<MockTtsPlatformImpl> nice_mock_platform_impl_;
}; };
IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakOptionalArgs) { IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakOptionalArgs) {
...@@ -311,6 +318,7 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakOptionalArgs) { ...@@ -311,6 +318,7 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakOptionalArgs) {
EXPECT_CALL(mock_platform_impl_, DoSpeak(_, "Echo", _, _, _)) EXPECT_CALL(mock_platform_impl_, DoSpeak(_, "Echo", _, _, _))
.WillOnce(Return()); .WillOnce(Return());
ASSERT_TRUE(RunExtensionTest("tts/optional_args")) << message_; ASSERT_TRUE(RunExtensionTest("tts/optional_args")) << message_;
IgnoreAnyAdditionalCallsToMockPlatformImpl();
} }
IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakFinishesImmediately) { IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakFinishesImmediately) {
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#include "content/browser/media/browser_feature_provider.h" #include "content/browser/media/browser_feature_provider.h"
#include "content/browser/permissions/permission_controller_impl.h" #include "content/browser/permissions/permission_controller_impl.h"
#include "content/browser/push_messaging/push_messaging_router.h" #include "content/browser/push_messaging/push_messaging_router.h"
#include "content/browser/speech/tts_controller_impl.h"
#include "content/browser/storage_partition_impl_map.h" #include "content/browser/storage_partition_impl_map.h"
#include "content/common/child_process_host_impl.h" #include "content/common/child_process_host_impl.h"
#include "content/public/browser/blob_handle.h" #include "content/public/browser/blob_handle.h"
...@@ -484,6 +485,8 @@ BrowserContext::~BrowserContext() { ...@@ -484,6 +485,8 @@ BrowserContext::~BrowserContext() {
if (GetUserData(kDownloadManagerKeyName)) if (GetUserData(kDownloadManagerKeyName))
GetDownloadManager(this)->Shutdown(); GetDownloadManager(this)->Shutdown();
TtsControllerImpl::GetInstance()->OnBrowserContextDestroyed(this);
TRACE_EVENT_NESTABLE_ASYNC_END1( TRACE_EVENT_NESTABLE_ASYNC_END1(
"shutdown", "BrowserContext::NotifyWillBeDestroyed() called.", this, "shutdown", "BrowserContext::NotifyWillBeDestroyed() called.", this,
"browser_context", this); "browser_context", this);
......
...@@ -98,14 +98,14 @@ void TtsControllerImpl::SpeakOrEnqueue( ...@@ -98,14 +98,14 @@ void TtsControllerImpl::SpeakOrEnqueue(
// If we're paused and we get an utterance that can't be queued, // If we're paused and we get an utterance that can't be queued,
// flush the queue but stay in the paused state. // flush the queue but stay in the paused state.
if (paused_ && !utterance->GetCanEnqueue()) { if (paused_ && !utterance->GetCanEnqueue()) {
utterance_queue_.emplace(std::move(utterance)); utterance_deque_.emplace_back(std::move(utterance));
Stop(); Stop();
paused_ = true; paused_ = true;
return; return;
} }
if (paused_ || (IsSpeaking() && utterance->GetCanEnqueue())) { if (paused_ || (IsSpeaking() && utterance->GetCanEnqueue())) {
utterance_queue_.emplace(std::move(utterance)); utterance_deque_.emplace_back(std::move(utterance));
} else { } else {
Stop(); Stop();
SpeakNow(std::move(utterance)); SpeakNow(std::move(utterance));
...@@ -113,11 +113,17 @@ void TtsControllerImpl::SpeakOrEnqueue( ...@@ -113,11 +113,17 @@ void TtsControllerImpl::SpeakOrEnqueue(
} }
void TtsControllerImpl::Stop() { void TtsControllerImpl::Stop() {
Stop(GURL()); StopInternal(GURL(), true);
} }
void TtsControllerImpl::Stop(const GURL& source_url) { void TtsControllerImpl::Stop(const GURL& source_url) {
base::RecordAction(base::UserMetricsAction("TextToSpeech.Stop")); StopInternal(source_url, true);
}
void TtsControllerImpl::StopInternal(const GURL& source_url,
bool record_user_action) {
if (record_user_action)
base::RecordAction(base::UserMetricsAction("TextToSpeech.Stop"));
paused_ = false; paused_ = false;
...@@ -272,13 +278,13 @@ void TtsControllerImpl::RemoveVoicesChangedDelegate( ...@@ -272,13 +278,13 @@ void TtsControllerImpl::RemoveVoicesChangedDelegate(
void TtsControllerImpl::RemoveUtteranceEventDelegate( void TtsControllerImpl::RemoveUtteranceEventDelegate(
UtteranceEventDelegate* delegate) { UtteranceEventDelegate* delegate) {
// First clear any pending utterances with this delegate. // First clear any pending utterances with this delegate.
base::queue<std::unique_ptr<TtsUtterance>> old_queue; std::deque<std::unique_ptr<TtsUtterance>> old_deque;
utterance_queue_.swap(old_queue); utterance_deque_.swap(old_deque);
while (!old_queue.empty()) { while (!old_deque.empty()) {
std::unique_ptr<TtsUtterance> utterance = std::move(old_queue.front()); std::unique_ptr<TtsUtterance> utterance = std::move(old_deque.front());
old_queue.pop(); old_deque.pop_front();
if (utterance->GetEventDelegate() != delegate) if (utterance->GetEventDelegate() != delegate)
utterance_queue_.emplace(std::move(utterance)); utterance_deque_.emplace_back(std::move(utterance));
} }
if (current_utterance_ && if (current_utterance_ &&
...@@ -313,12 +319,38 @@ TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() { ...@@ -313,12 +319,38 @@ TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() {
return GetTtsControllerDelegate()->GetTtsEngineDelegate(); return GetTtsControllerDelegate()->GetTtsEngineDelegate();
} }
void TtsControllerImpl::OnBrowserContextDestroyed(
BrowserContext* browser_context) {
bool did_clear_utterances = false;
// First clear the BrowserContext from any utterances.
for (std::unique_ptr<TtsUtterance>& utterance : utterance_deque_) {
if (utterance->GetBrowserContext() == browser_context) {
utterance->ClearBrowserContext();
did_clear_utterances = true;
}
}
if (current_utterance_ &&
current_utterance_->GetBrowserContext() == browser_context) {
current_utterance_->ClearBrowserContext();
did_clear_utterances = true;
}
// If we cleared the BrowserContext from any utterances, stop speech
// just to be safe. Don't record user actions because that crashes in
// tests because last_active_profile_ isn't cleared.
if (did_clear_utterances) {
StopInternal(GURL(), false);
}
}
void TtsControllerImpl::SetTtsPlatform(TtsPlatform* tts_platform) { void TtsControllerImpl::SetTtsPlatform(TtsPlatform* tts_platform) {
tts_platform_ = tts_platform; tts_platform_ = tts_platform;
} }
int TtsControllerImpl::QueueSize() { int TtsControllerImpl::QueueSize() {
return static_cast<int>(utterance_queue_.size()); return static_cast<int>(utterance_deque_.size());
} }
TtsPlatform* TtsControllerImpl::GetTtsPlatform() { TtsPlatform* TtsControllerImpl::GetTtsPlatform() {
...@@ -417,7 +449,7 @@ void TtsControllerImpl::OnSpeakFinished(int utterance_id, bool success) { ...@@ -417,7 +449,7 @@ void TtsControllerImpl::OnSpeakFinished(int utterance_id, bool success) {
// the browser has built-in TTS that isn't loaded yet. // the browser has built-in TTS that isn't loaded yet.
if (GetTtsPlatform()->LoadBuiltInTtsEngine( if (GetTtsPlatform()->LoadBuiltInTtsEngine(
current_utterance_->GetBrowserContext())) { current_utterance_->GetBrowserContext())) {
utterance_queue_.emplace(std::move(current_utterance_)); utterance_deque_.emplace_back(std::move(current_utterance_));
return; return;
} }
...@@ -427,10 +459,10 @@ void TtsControllerImpl::OnSpeakFinished(int utterance_id, bool success) { ...@@ -427,10 +459,10 @@ void TtsControllerImpl::OnSpeakFinished(int utterance_id, bool success) {
} }
void TtsControllerImpl::ClearUtteranceQueue(bool send_events) { void TtsControllerImpl::ClearUtteranceQueue(bool send_events) {
while (!utterance_queue_.empty()) { while (!utterance_deque_.empty()) {
std::unique_ptr<TtsUtterance> utterance = std::unique_ptr<TtsUtterance> utterance =
std::move(utterance_queue_.front()); std::move(utterance_deque_.front());
utterance_queue_.pop(); utterance_deque_.pop_front();
if (send_events) { if (send_events) {
utterance->OnTtsEvent(TTS_EVENT_CANCELLED, kInvalidCharIndex, utterance->OnTtsEvent(TTS_EVENT_CANCELLED, kInvalidCharIndex,
kInvalidLength, std::string()); kInvalidLength, std::string());
...@@ -455,10 +487,10 @@ void TtsControllerImpl::SpeakNextUtterance() { ...@@ -455,10 +487,10 @@ void TtsControllerImpl::SpeakNextUtterance() {
// Start speaking the next utterance in the queue. Keep trying in case // Start speaking the next utterance in the queue. Keep trying in case
// one fails but there are still more in the queue to try. // one fails but there are still more in the queue to try.
while (!utterance_queue_.empty() && !current_utterance_) { while (!utterance_deque_.empty() && !current_utterance_) {
std::unique_ptr<TtsUtterance> utterance = std::unique_ptr<TtsUtterance> utterance =
std::move(utterance_queue_.front()); std::move(utterance_deque_.front());
utterance_queue_.pop(); utterance_deque_.pop_front();
SpeakNow(std::move(utterance)); SpeakNow(std::move(utterance));
} }
} }
......
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
#ifndef CONTENT_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_ #ifndef CONTENT_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_
#define CONTENT_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_ #define CONTENT_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_
#include <deque>
#include <memory> #include <memory>
#include <set> #include <set>
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/containers/queue.h"
#include "base/gtest_prod_util.h" #include "base/gtest_prod_util.h"
#include "base/json/json_reader.h" #include "base/json/json_reader.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -59,6 +59,10 @@ class CONTENT_EXPORT TtsControllerImpl : public TtsController { ...@@ -59,6 +59,10 @@ class CONTENT_EXPORT TtsControllerImpl : public TtsController {
void SetTtsEngineDelegate(TtsEngineDelegate* delegate) override; void SetTtsEngineDelegate(TtsEngineDelegate* delegate) override;
TtsEngineDelegate* GetTtsEngineDelegate() override; TtsEngineDelegate* GetTtsEngineDelegate() override;
// Called directly by ~BrowserContext, because a raw BrowserContext pointer
// is stored in an Utterance.
void OnBrowserContextDestroyed(BrowserContext* browser_context);
// Testing methods // Testing methods
void SetTtsPlatform(TtsPlatform* tts_platform) override; void SetTtsPlatform(TtsPlatform* tts_platform) override;
int QueueSize() override; int QueueSize() override;
...@@ -77,6 +81,7 @@ class CONTENT_EXPORT TtsControllerImpl : public TtsController { ...@@ -77,6 +81,7 @@ class CONTENT_EXPORT TtsControllerImpl : public TtsController {
FRIEND_TEST_ALL_PREFIXES(TtsControllerTest, TestGetMatchingVoice); FRIEND_TEST_ALL_PREFIXES(TtsControllerTest, TestGetMatchingVoice);
FRIEND_TEST_ALL_PREFIXES(TtsControllerTest, FRIEND_TEST_ALL_PREFIXES(TtsControllerTest,
TestTtsControllerUtteranceDefaults); TestTtsControllerUtteranceDefaults);
FRIEND_TEST_ALL_PREFIXES(TtsControllerTest, TestBrowserContextRemoved);
friend struct base::DefaultSingletonTraits<TtsControllerImpl>; friend struct base::DefaultSingletonTraits<TtsControllerImpl>;
...@@ -87,6 +92,10 @@ class CONTENT_EXPORT TtsControllerImpl : public TtsController { ...@@ -87,6 +92,10 @@ class CONTENT_EXPORT TtsControllerImpl : public TtsController {
// |utterance| or delete it if there's an error. Returns true on success. // |utterance| or delete it if there's an error. Returns true on success.
void SpeakNow(std::unique_ptr<TtsUtterance> utterance); void SpeakNow(std::unique_ptr<TtsUtterance> utterance);
// Implementation of Stop(), with an optional flag to indicate whether
// user actions should be recorded.
void StopInternal(const GURL& source_url, bool record_user_action);
// Clear the utterance queue. If send_events is true, will send // Clear the utterance queue. If send_events is true, will send
// TTS_EVENT_CANCELLED events on each one. // TTS_EVENT_CANCELLED events on each one.
void ClearUtteranceQueue(bool send_events); void ClearUtteranceQueue(bool send_events);
...@@ -131,7 +140,7 @@ class CONTENT_EXPORT TtsControllerImpl : public TtsController { ...@@ -131,7 +140,7 @@ class CONTENT_EXPORT TtsControllerImpl : public TtsController {
TtsPlatform* tts_platform_; TtsPlatform* tts_platform_;
// A queue of utterances to speak after the current one finishes. // A queue of utterances to speak after the current one finishes.
base::queue<std::unique_ptr<TtsUtterance>> utterance_queue_; std::deque<std::unique_ptr<TtsUtterance>> utterance_deque_;
DISALLOW_COPY_AND_ASSIGN(TtsControllerImpl); DISALLOW_COPY_AND_ASSIGN(TtsControllerImpl);
}; };
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include "content/browser/speech/tts_controller_impl.h" #include "content/browser/speech/tts_controller_impl.h"
#include "content/public/browser/tts_controller_delegate.h" #include "content/public/browser/tts_controller_delegate.h"
#include "content/public/browser/tts_platform.h" #include "content/public/browser/tts_platform.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/speech/speech_synthesis.mojom.h" #include "third_party/blink/public/mojom/speech/speech_synthesis.mojom.h"
...@@ -50,9 +52,15 @@ class MockTtsControllerDelegate : public TtsControllerDelegate { ...@@ -50,9 +52,15 @@ class MockTtsControllerDelegate : public TtsControllerDelegate {
MockTtsControllerDelegate() {} MockTtsControllerDelegate() {}
~MockTtsControllerDelegate() override {} ~MockTtsControllerDelegate() override {}
BrowserContext* GetLastBrowserContext() {
BrowserContext* result = last_browser_context_;
last_browser_context_ = nullptr;
return result;
}
int GetMatchingVoice(content::TtsUtterance* utterance, int GetMatchingVoice(content::TtsUtterance* utterance,
std::vector<content::VoiceData>& voices) override { std::vector<content::VoiceData>& voices) override {
// Below 0 implies a "native" voice. last_browser_context_ = utterance->GetBrowserContext();
return -1; return -1;
} }
...@@ -66,6 +74,9 @@ class MockTtsControllerDelegate : public TtsControllerDelegate { ...@@ -66,6 +74,9 @@ class MockTtsControllerDelegate : public TtsControllerDelegate {
content::TtsEngineDelegate* GetTtsEngineDelegate() override { content::TtsEngineDelegate* GetTtsEngineDelegate() override {
return nullptr; return nullptr;
} }
private:
BrowserContext* last_browser_context_ = nullptr;
}; };
// Subclass of TtsController with a public ctor and dtor. // Subclass of TtsController with a public ctor and dtor.
...@@ -101,6 +112,45 @@ TEST_F(TtsControllerTest, TestTtsControllerShutdown) { ...@@ -101,6 +112,45 @@ TEST_F(TtsControllerTest, TestTtsControllerShutdown) {
delete delegate; delete delegate;
} }
TEST_F(TtsControllerTest, TestBrowserContextRemoved) {
// Create a controller, mock other stuff, and create a test
// browser context.
TtsControllerImpl* controller = TtsControllerImpl::GetInstance();
MockTtsPlatformImpl platform_impl;
MockTtsControllerDelegate delegate;
controller->delegate_ = &delegate;
controller->SetTtsPlatform(&platform_impl);
content::BrowserTaskEnvironment task_environment;
auto browser_context = std::make_unique<TestBrowserContext>();
// Speak an utterances associated with this test browser context.
std::unique_ptr<TtsUtterance> utterance1 =
TtsUtterance::Create(browser_context.get());
utterance1->SetCanEnqueue(true);
utterance1->SetSrcId(1);
controller->SpeakOrEnqueue(std::move(utterance1));
// Assert that the delegate was called and it got our browser context.
ASSERT_EQ(browser_context.get(), delegate.GetLastBrowserContext());
// Now queue up a second utterance to be spoken, also associated with
// this browser context.
std::unique_ptr<TtsUtterance> utterance2 =
TtsUtterance::Create(browser_context.get());
utterance2->SetCanEnqueue(true);
utterance2->SetSrcId(2);
controller->SpeakOrEnqueue(std::move(utterance2));
// Destroy the browser context before the utterance is spoken.
browser_context.reset();
// Now speak the next utterance, and ensure that we don't get the
// destroyed browser context.
controller->FinishCurrentUtterance();
controller->SpeakNextUtterance();
ASSERT_EQ(nullptr, delegate.GetLastBrowserContext());
}
#if !defined(OS_CHROMEOS) #if !defined(OS_CHROMEOS)
TEST_F(TtsControllerTest, TestTtsControllerUtteranceDefaults) { TEST_F(TtsControllerTest, TestTtsControllerUtteranceDefaults) {
std::unique_ptr<TtsControllerForTesting> controller = std::unique_ptr<TtsControllerForTesting> controller =
......
...@@ -182,6 +182,10 @@ BrowserContext* TtsUtteranceImpl::GetBrowserContext() { ...@@ -182,6 +182,10 @@ BrowserContext* TtsUtteranceImpl::GetBrowserContext() {
return browser_context_; return browser_context_;
} }
void TtsUtteranceImpl::ClearBrowserContext() {
browser_context_ = nullptr;
}
int TtsUtteranceImpl::GetId() { int TtsUtteranceImpl::GetId() {
return id_; return id_;
} }
......
...@@ -68,6 +68,8 @@ class CONTENT_EXPORT TtsUtteranceImpl : public TtsUtterance { ...@@ -68,6 +68,8 @@ class CONTENT_EXPORT TtsUtteranceImpl : public TtsUtterance {
UtteranceEventDelegate* GetEventDelegate() override; UtteranceEventDelegate* GetEventDelegate() override;
BrowserContext* GetBrowserContext() override; BrowserContext* GetBrowserContext() override;
void ClearBrowserContext() override;
int GetId() override; int GetId() override;
bool IsFinished() override; bool IsFinished() override;
...@@ -102,7 +104,7 @@ class CONTENT_EXPORT TtsUtteranceImpl : public TtsUtterance { ...@@ -102,7 +104,7 @@ class CONTENT_EXPORT TtsUtteranceImpl : public TtsUtterance {
GURL src_url_; GURL src_url_;
// The delegate to be called when an utterance event is fired. // The delegate to be called when an utterance event is fired.
UtteranceEventDelegate* event_delegate_; UtteranceEventDelegate* event_delegate_ = nullptr;
// The parsed options. // The parsed options.
std::string voice_name_; std::string voice_name_;
......
...@@ -118,6 +118,7 @@ class CONTENT_EXPORT TtsUtterance { ...@@ -118,6 +118,7 @@ class CONTENT_EXPORT TtsUtterance {
// Getters and setters for internal state. // Getters and setters for internal state.
virtual BrowserContext* GetBrowserContext() = 0; virtual BrowserContext* GetBrowserContext() = 0;
virtual void ClearBrowserContext() = 0;
virtual int GetId() = 0; virtual int GetId() = 0;
virtual bool IsFinished() = 0; virtual bool IsFinished() = 0;
}; };
......
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