Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 873b0d9

Browse files
committed
Bug 1682928 - Protect shared memory locations behind a mutex. r=asuth, a=tjr
Differential Revision: https://phabricator.services.mozilla.com/D101582
1 parent f6ed5ed commit 873b0d9

File tree

1 file changed

+110
-65
lines changed

1 file changed

+110
-65
lines changed

dom/base/EventSource.cpp

Lines changed: 110 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "mozilla/dom/EventSource.h"
88

99
#include "mozilla/ArrayUtils.h"
10+
#include "mozilla/DataMutex.h"
1011
#include "mozilla/DebugOnly.h"
1112
#include "mozilla/LoadInfo.h"
1213
#include "mozilla/DOMEventTargetHelper.h"
@@ -147,25 +148,29 @@ class EventSourceImpl final : public nsIObserver,
147148
bool IsTargetThread() const { return NS_GetCurrentThread() == mTargetThread; }
148149

149150
uint16_t ReadyState() {
150-
MutexAutoLock lock(mMutex);
151-
if (mEventSource) {
152-
return mEventSource->mReadyState;
151+
auto lock = mSharedData.Lock();
152+
if (lock->mEventSource) {
153+
return lock->mEventSource->mReadyState;
153154
}
154155
// EventSourceImpl keeps EventSource alive. If mEventSource is null, it
155156
// means that the EventSource has been closed.
156157
return CLOSED;
157158
}
158159

159160
void SetReadyState(uint16_t aReadyState) {
160-
MutexAutoLock lock(mMutex);
161-
MOZ_ASSERT(mEventSource);
161+
auto lock = mSharedData.Lock();
162+
MOZ_ASSERT(lock->mEventSource);
162163
MOZ_ASSERT(!mIsShutDown);
163-
mEventSource->mReadyState = aReadyState;
164+
lock->mEventSource->mReadyState = aReadyState;
164165
}
165166

166167
bool IsClosed() { return ReadyState() == CLOSED; }
167168

168-
RefPtr<EventSource> mEventSource;
169+
RefPtr<EventSource> GetEventSource() {
170+
AssertIsOnTargetThread();
171+
auto lock = mSharedData.Lock();
172+
return lock->mEventSource;
173+
}
169174

170175
/**
171176
* A simple state machine used to manage the event-source's line buffer
@@ -249,9 +254,6 @@ class EventSourceImpl final : public nsIObserver,
249254
// EventSourceImpl internal states.
250255
// WorkerRef to keep the worker alive. (accessed on worker thread only)
251256
RefPtr<ThreadSafeWorkerRef> mWorkerRef;
252-
// This mutex protects mServiceNotifier and mEventSource->mReadyState that are
253-
// used in different threads.
254-
mozilla::Mutex mMutex;
255257
// Whether the window is frozen. May be set on main thread and read on target
256258
// thread.
257259
Atomic<bool> mFrozen;
@@ -311,7 +313,13 @@ class EventSourceImpl final : public nsIObserver,
311313
bool mConnectionOpened;
312314
};
313315

314-
UniquePtr<EventSourceServiceNotifier> mServiceNotifier;
316+
struct SharedData {
317+
RefPtr<EventSource> mEventSource;
318+
UniquePtr<EventSourceServiceNotifier> mServiceNotifier;
319+
};
320+
321+
DataMutex<SharedData> mSharedData;
322+
315323
// Event Source owner information:
316324
// - the script file name
317325
// - source code line number and column number where the Event Source object
@@ -355,20 +363,19 @@ NS_IMPL_ISUPPORTS(EventSourceImpl, nsIObserver, nsIStreamListener,
355363

356364
EventSourceImpl::EventSourceImpl(EventSource* aEventSource,
357365
nsICookieJarSettings* aCookieJarSettings)
358-
: mEventSource(aEventSource),
359-
mReconnectionTime(0),
366+
: mReconnectionTime(0),
360367
mStatus(PARSE_STATE_OFF),
361-
mMutex("EventSourceImpl::mMutex"),
362368
mFrozen(false),
363369
mGoingToDispatchAllMessages(false),
364370
mIsMainThread(NS_IsMainThread()),
365371
mIsShutDown(false),
372+
mSharedData(SharedData{aEventSource}, "EventSourceImpl::mSharedData"),
366373
mScriptLine(0),
367374
mScriptColumn(0),
368375
mInnerWindowID(0),
369376
mCookieJarSettings(aCookieJarSettings),
370377
mTargetThread(NS_GetCurrentThread()) {
371-
MOZ_ASSERT(mEventSource);
378+
MOZ_ASSERT(aEventSource);
372379
SetReadyState(CONNECTING);
373380
}
374381

@@ -412,13 +419,13 @@ void EventSourceImpl::CloseInternal() {
412419

413420
RefPtr<EventSource> myES;
414421
{
415-
MutexAutoLock lock(mMutex);
422+
auto lock = mSharedData.Lock();
416423
// We want to ensure to release ourself even if we have
417424
// the shutdown case, thus we put aside a pointer
418425
// to the EventSource and null it out right now.
419-
myES = std::move(mEventSource);
420-
mEventSource = nullptr;
421-
mServiceNotifier = nullptr;
426+
myES = std::move(lock->mEventSource);
427+
lock->mEventSource = nullptr;
428+
lock->mServiceNotifier = nullptr;
422429
}
423430

424431
MOZ_ASSERT(!mIsShutDown);
@@ -569,7 +576,12 @@ nsresult EventSourceImpl::ParseURL(const nsAString& aURL) {
569576
// is only ever called from EventSourceImpl::Init(), which is either called
570577
// directly if mEventSource was created on the main thread, or via a
571578
// synchronous runnable if it was created on a worker thread.
572-
mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
579+
{
580+
// We can't use GetEventSource() here because it would modify the refcount,
581+
// and that's not allowed off the owning thread.
582+
auto lock = mSharedData.Lock();
583+
lock->mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
584+
}
573585
mSrc = srcURI;
574586
mOrigin = origin;
575587
return NS_OK;
@@ -649,8 +661,12 @@ EventSourceImpl::Observe(nsISupports* aSubject, const char* aTopic,
649661

650662
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
651663
MOZ_ASSERT(mIsMainThread);
652-
if (!mEventSource->GetOwner() || window != mEventSource->GetOwner()) {
653-
return NS_OK;
664+
{
665+
auto lock = mSharedData.Lock();
666+
if (!lock->mEventSource->GetOwner() ||
667+
window != lock->mEventSource->GetOwner()) {
668+
return NS_OK;
669+
}
654670
}
655671

656672
DebugOnly<nsresult> rv;
@@ -723,8 +739,8 @@ EventSourceImpl::OnStartRequest(nsIRequest* aRequest) {
723739
}
724740

725741
{
726-
MutexAutoLock lock(mMutex);
727-
mServiceNotifier = MakeUnique<EventSourceServiceNotifier>(
742+
auto lock = mSharedData.Lock();
743+
lock->mServiceNotifier = MakeUnique<EventSourceServiceNotifier>(
728744
this, mHttpChannel->ChannelId(), mInnerWindowID);
729745
}
730746
rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::AnnounceConnection",
@@ -867,7 +883,8 @@ EventSourceImpl::AsyncOnChannelRedirect(
867883

868884
bool isValidScheme = newURI->SchemeIs("http") || newURI->SchemeIs("https");
869885

870-
rv = mIsMainThread ? mEventSource->CheckCurrentGlobalCorrectness() : NS_OK;
886+
rv =
887+
mIsMainThread ? GetEventSource()->CheckCurrentGlobalCorrectness() : NS_OK;
871888
if (NS_FAILED(rv) || !isValidScheme) {
872889
DispatchFailConnection();
873890
return NS_ERROR_DOM_SECURITY_ERR;
@@ -922,11 +939,12 @@ EventSourceImpl::GetInterface(const nsIID& aIID, void** aResult) {
922939
// To avoid a data race we may only access the event target if it lives on
923940
// the main thread.
924941
if (mIsMainThread) {
925-
rv = mEventSource->CheckCurrentGlobalCorrectness();
942+
auto lock = mSharedData.Lock();
943+
rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
926944
NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
927945

928-
if (mEventSource->GetOwner()) {
929-
window = mEventSource->GetOwner()->GetOuterWindow();
946+
if (lock->mEventSource->GetOwner()) {
947+
window = lock->mEventSource->GetOwner()->GetOuterWindow();
930948
}
931949
}
932950

@@ -959,7 +977,7 @@ nsresult EventSourceImpl::GetBaseURI(nsIURI** aBaseURI) {
959977

960978
// first we try from document->GetBaseURI()
961979
nsCOMPtr<Document> doc =
962-
mIsMainThread ? mEventSource->GetDocumentIfCurrent() : nullptr;
980+
mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
963981
if (doc) {
964982
baseURI = doc->GetBaseURI();
965983
}
@@ -1029,31 +1047,41 @@ nsresult EventSourceImpl::InitChannelAndRequestEventSource(
10291047
bool isValidScheme = mSrc->SchemeIs("http") || mSrc->SchemeIs("https");
10301048

10311049
MOZ_ASSERT_IF(mIsMainThread, aEventTargetAccessAllowed);
1050+
10321051
nsresult rv = aEventTargetAccessAllowed
1033-
? mEventSource->CheckCurrentGlobalCorrectness()
1052+
? [this]() {
1053+
// We can't call GetEventSource() because we're not
1054+
// allowed to touch the refcount off the worker thread
1055+
// due to an assertion, event if it would have otherwise
1056+
// been safe.
1057+
auto lock = mSharedData.Lock();
1058+
return lock->mEventSource->CheckCurrentGlobalCorrectness();
1059+
}()
10341060
: NS_OK;
10351061
if (NS_FAILED(rv) || !isValidScheme) {
10361062
DispatchFailConnection();
10371063
return NS_ERROR_DOM_SECURITY_ERR;
10381064
}
10391065

1066+
nsCOMPtr<Document> doc;
1067+
nsSecurityFlags securityFlags =
1068+
nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
1069+
{
1070+
auto lock = mSharedData.Lock();
1071+
doc = aEventTargetAccessAllowed ? lock->mEventSource->GetDocumentIfCurrent()
1072+
: nullptr;
1073+
1074+
if (lock->mEventSource->mWithCredentials) {
1075+
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1076+
}
1077+
}
1078+
10401079
// The html spec requires we use fetch cache mode of "no-store". This
10411080
// maps to LOAD_BYPASS_CACHE and LOAD_INHIBIT_CACHING in necko.
10421081
nsLoadFlags loadFlags;
10431082
loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE |
10441083
nsIRequest::INHIBIT_CACHING;
10451084

1046-
const nsCOMPtr<Document> doc = aEventTargetAccessAllowed
1047-
? mEventSource->GetDocumentIfCurrent()
1048-
: nullptr;
1049-
1050-
nsSecurityFlags securityFlags =
1051-
nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
1052-
1053-
if (mEventSource->mWithCredentials) {
1054-
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1055-
}
1056-
10571085
nsCOMPtr<nsIChannel> channel;
10581086
// If we have the document, use it
10591087
if (doc) {
@@ -1115,9 +1143,9 @@ void EventSourceImpl::AnnounceConnection() {
11151143
}
11161144

11171145
{
1118-
MutexAutoLock lock(mMutex);
1119-
if (mServiceNotifier) {
1120-
mServiceNotifier->ConnectionOpened();
1146+
auto lock = mSharedData.Lock();
1147+
if (lock->mServiceNotifier) {
1148+
lock->mServiceNotifier->ConnectionOpened();
11211149
}
11221150
}
11231151

@@ -1127,11 +1155,13 @@ void EventSourceImpl::AnnounceConnection() {
11271155

11281156
SetReadyState(OPEN);
11291157

1130-
nsresult rv = mEventSource->CheckCurrentGlobalCorrectness();
1158+
nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
11311159
if (NS_FAILED(rv)) {
11321160
return;
11331161
}
1134-
rv = mEventSource->CreateAndDispatchSimpleEvent(u"open"_ns);
1162+
// We can't hold the mutex while dispatching the event because the mutex is
1163+
// not reentrant, and content might call back into our code.
1164+
rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"open"_ns);
11351165
if (NS_FAILED(rv)) {
11361166
NS_WARNING("Failed to dispatch the error event!!!");
11371167
return;
@@ -1212,14 +1242,16 @@ void EventSourceImpl::ReestablishConnection() {
12121242
return;
12131243
}
12141244

1215-
rv = mEventSource->CheckCurrentGlobalCorrectness();
1245+
rv = GetEventSource()->CheckCurrentGlobalCorrectness();
12161246
if (NS_FAILED(rv)) {
12171247
return;
12181248
}
12191249

12201250
SetReadyState(CONNECTING);
12211251
ResetDecoder();
1222-
rv = mEventSource->CreateAndDispatchSimpleEvent(u"error"_ns);
1252+
// We can't hold the mutex while dispatching the event because the mutex is
1253+
// not reentrant, and content might call back into our code.
1254+
rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
12231255
if (NS_FAILED(rv)) {
12241256
NS_WARNING("Failed to dispatch the error event!!!");
12251257
return;
@@ -1338,9 +1370,11 @@ void EventSourceImpl::FailConnection() {
13381370
// When a user agent is to fail the connection, the user agent must set the
13391371
// readyState attribute to CLOSED and queue a task to fire a simple event
13401372
// named error at the EventSource object.
1341-
nsresult rv = mEventSource->CheckCurrentGlobalCorrectness();
1373+
nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
13421374
if (NS_SUCCEEDED(rv)) {
1343-
rv = mEventSource->CreateAndDispatchSimpleEvent(u"error"_ns);
1375+
// We can't hold the mutex while dispatching the event because the mutex
1376+
// is not reentrant, and content might call back into our code.
1377+
rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
13441378
if (NS_FAILED(rv)) {
13451379
NS_WARNING("Failed to dispatch the error event!!!");
13461380
}
@@ -1449,14 +1483,18 @@ void EventSourceImpl::DispatchAllMessageEvents() {
14491483
return;
14501484
}
14511485

1452-
nsresult rv = mEventSource->CheckCurrentGlobalCorrectness();
1453-
if (NS_FAILED(rv)) {
1454-
return;
1455-
}
1456-
1486+
nsresult rv;
14571487
AutoJSAPI jsapi;
1458-
if (NS_WARN_IF(!jsapi.Init(mEventSource->GetOwnerGlobal()))) {
1459-
return;
1488+
{
1489+
auto lock = mSharedData.Lock();
1490+
rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
1491+
if (NS_FAILED(rv)) {
1492+
return;
1493+
}
1494+
1495+
if (NS_WARN_IF(!jsapi.Init(lock->mEventSource->GetOwnerGlobal()))) {
1496+
return;
1497+
}
14601498
}
14611499

14621500
JSContext* cx = jsapi.cx();
@@ -1473,11 +1511,11 @@ void EventSourceImpl::DispatchAllMessageEvents() {
14731511
}
14741512

14751513
{
1476-
MutexAutoLock lock(mMutex);
1477-
if (mServiceNotifier) {
1478-
mServiceNotifier->EventReceived(message->mEventName, mLastEventID,
1479-
message->mData, mReconnectionTime,
1480-
PR_Now());
1514+
auto lock = mSharedData.Lock();
1515+
if (lock->mServiceNotifier) {
1516+
lock->mServiceNotifier->EventReceived(message->mEventName, mLastEventID,
1517+
message->mData, mReconnectionTime,
1518+
PR_Now());
14811519
}
14821520
}
14831521

@@ -1495,16 +1533,19 @@ void EventSourceImpl::DispatchAllMessageEvents() {
14951533
// create an event that uses the MessageEvent interface,
14961534
// which does not bubble, is not cancelable, and has no default action
14971535

1536+
RefPtr<EventSource> eventSource = GetEventSource();
14981537
RefPtr<MessageEvent> event =
1499-
new MessageEvent(mEventSource, nullptr, nullptr);
1538+
new MessageEvent(eventSource, nullptr, nullptr);
15001539

15011540
event->InitMessageEvent(nullptr, message->mEventName, CanBubble::eNo,
15021541
Cancelable::eNo, jsData, mOrigin, mLastEventID,
15031542
nullptr, Sequence<OwningNonNull<MessagePort>>());
15041543
event->SetTrusted(true);
15051544

1545+
// We can't hold the mutex while dispatching the event because the mutex is
1546+
// not reentrant, and content might call back into our code.
15061547
IgnoredErrorResult err;
1507-
mEventSource->DispatchEvent(*event, err);
1548+
eventSource->DispatchEvent(*event, err);
15081549
if (err.Failed()) {
15091550
NS_WARNING("Failed to dispatch the message event!!!");
15101551
return;
@@ -2055,7 +2096,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
20552096

20562097
bool EventSource::IsCertainlyAliveForCC() const {
20572098
// Until we are double linked forth and back, we want to stay alive.
2058-
return mESImpl != nullptr && mESImpl->mEventSource == this;
2099+
if (!mESImpl) {
2100+
return false;
2101+
}
2102+
auto lock = mESImpl->mSharedData.Lock();
2103+
return lock->mEventSource == this;
20592104
}
20602105

20612106
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventSource)

0 commit comments

Comments
 (0)