Bug 1337537 - Assert that runnables labeled with a given TabGroup never touch other TabGroups (r=ehsan)

MozReview-Commit-ID: sOdn9e5f57
This commit is contained in:
Bill McCloskey 2017-02-01 13:38:33 -08:00
parent c46113590f
commit 1ae37fef66
8 changed files with 165 additions and 3 deletions

View File

@ -70,5 +70,11 @@ DocGroup::AbstractMainThreadForImpl(TaskCategory aCategory)
return mTabGroup->AbstractMainThreadFor(aCategory);
}
bool*
DocGroup::GetValidAccessPtr()
{
return mTabGroup->GetValidAccessPtr();
}
}
}

View File

@ -12,6 +12,7 @@
#include "nsTHashtable.h"
#include "nsString.h"
#include "mozilla/dom/TabGroup.h"
#include "mozilla/Dispatcher.h"
#include "mozilla/RefPtr.h"
@ -34,8 +35,6 @@ namespace dom {
// (through its DocGroups) the documents from one or more tabs related by
// window.opener. A DocGroup is a member of exactly one TabGroup.
class TabGroup;
class DocGroup final : public Dispatcher
{
public:
@ -76,6 +75,17 @@ public:
virtual nsIEventTarget* EventTargetFor(TaskCategory aCategory) const override;
// Ensure that it's valid to access the DocGroup at this time.
void ValidateAccess() const
{
mTabGroup->ValidateAccess();
}
// Return a pointer that can be continually checked to see if access to this
// DocGroup is valid. This pointer should live at least as long as the
// DocGroup.
bool* GetValidAccessPtr();
private:
virtual AbstractThread*
AbstractMainThreadForImpl(TaskCategory aCategory) override;

View File

@ -56,6 +56,7 @@ private:
};
typedef nsTHashtable<HashEntry> DocGroupMap;
public:
typedef DocGroupMap::Iterator Iterator;

View File

@ -4987,6 +4987,14 @@ nsDocument::MaybeEndOutermostXBLUpdate()
void
nsDocument::BeginUpdate(nsUpdateType aUpdateType)
{
// If the document is going away, then it's probably okay to do things to it
// in the wrong DocGroup. We're unlikely to run JS or do anything else
// observable at this point. We reach this point when cycle collecting a
// <link> element and the unlink code removes a style sheet.
if (mDocGroup && !mIsGoingAway) {
mDocGroup->ValidateAccess();
}
if (mUpdateNestLevel == 0 && !mInXBLUpdate) {
mInXBLUpdate = true;
BindingManager()->BeginOutermostUpdate();
@ -7842,6 +7850,10 @@ nsDocument::GetExistingListenerManager() const
nsresult
nsDocument::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
if (mDocGroup && aVisitor.mEvent->mMessage != eVoidEvent) {
mDocGroup->ValidateAccess();
}
aVisitor.mCanHandle = true;
// FIXME! This is a hack to make middle mouse paste working also in Editor.
// Bug 329119

View File

@ -3148,6 +3148,17 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument,
newInnerWindow->mChromeEventHandler = mChromeEventHandler;
}
// Ask the JS engine to assert that it's valid to access our DocGroup whenever
// it runs JS code for this compartment. We skip the check if this window is
// for chrome JS or an add-on.
nsCOMPtr<nsIPrincipal> principal = mDoc->NodePrincipal();
nsString addonId;
principal->GetAddonId(addonId);
if (GetDocGroup() && !nsContentUtils::IsSystemPrincipal(principal) && addonId.IsEmpty()) {
js::SetCompartmentValidAccessPtr(cx, newInnerGlobal,
newInnerWindow->GetDocGroup()->GetValidAccessPtr());
}
nsJSContext::PokeGC(JS::gcreason::SET_NEW_DOCUMENT, GetWrapperPreserveColor());
kungFuDeathGrip->DidInitializeContext();

View File

@ -6,6 +6,7 @@
#include "mozilla/Dispatcher.h"
#include "jsfriendapi.h"
#include "mozilla/AbstractThread.h"
#include "mozilla/Move.h"
#include "nsINamed.h"
@ -15,6 +16,21 @@
using namespace mozilla;
class ValidatingDispatcher::Runnable final : public mozilla::Runnable
{
public:
Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
ValidatingDispatcher* aDispatcher);
NS_DECL_NSIRUNNABLE
private:
nsCOMPtr<nsIRunnable> mRunnable;
RefPtr<ValidatingDispatcher> mDispatcher;
};
/* DispatcherEventTarget */
namespace {
#define NS_DISPATCHEREVENTTARGET_IID \
@ -102,7 +118,10 @@ Dispatcher::UnlabeledDispatch(const char* aName,
}
}
ValidatingDispatcher* ValidatingDispatcher::sRunningDispatcher;
ValidatingDispatcher::ValidatingDispatcher()
: mAccessValid(false)
{
}
@ -111,7 +130,7 @@ ValidatingDispatcher::Dispatch(const char* aName,
TaskCategory aCategory,
already_AddRefed<nsIRunnable>&& aRunnable)
{
return UnlabeledDispatch(aName, aCategory, Move(aRunnable));
return LabeledDispatch(aName, aCategory, Move(aRunnable));
}
nsIEventTarget*
@ -182,3 +201,70 @@ ValidatingDispatcher::FromEventTarget(nsIEventTarget* aEventTarget)
}
return target->Dispatcher();
}
nsresult
ValidatingDispatcher::LabeledDispatch(const char* aName,
TaskCategory aCategory,
already_AddRefed<nsIRunnable>&& aRunnable)
{
nsCOMPtr<nsIRunnable> runnable(aRunnable);
if (XRE_IsContentProcess()) {
runnable = new Runnable(runnable.forget(), this);
}
return UnlabeledDispatch(aName, aCategory, runnable.forget());
}
void
ValidatingDispatcher::SetValidatingAccess(ValidationType aType)
{
sRunningDispatcher = aType == StartValidation ? this : nullptr;
mAccessValid = aType == StartValidation;
dom::AutoJSAPI jsapi;
jsapi.Init();
js::EnableAccessValidation(jsapi.cx(), !!sRunningDispatcher);
}
ValidatingDispatcher::Runnable::Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
ValidatingDispatcher* aDispatcher)
: mRunnable(Move(aRunnable)),
mDispatcher(aDispatcher)
{
}
NS_IMETHODIMP
ValidatingDispatcher::Runnable::Run()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
mDispatcher->SetValidatingAccess(StartValidation);
nsresult result = mRunnable->Run();
// The runnable's destructor can have side effects, so try to execute it in
// the scope of the TabGroup.
mRunnable = nullptr;
mDispatcher->SetValidatingAccess(EndValidation);
return result;
}
ValidatingDispatcher::AutoProcessEvent::AutoProcessEvent()
: mPrevRunningDispatcher(ValidatingDispatcher::sRunningDispatcher)
{
ValidatingDispatcher* prev = sRunningDispatcher;
if (prev) {
MOZ_ASSERT(prev->mAccessValid);
prev->SetValidatingAccess(EndValidation);
}
}
ValidatingDispatcher::AutoProcessEvent::~AutoProcessEvent()
{
MOZ_ASSERT(!sRunningDispatcher);
ValidatingDispatcher* prev = mPrevRunningDispatcher;
if (prev) {
prev->SetValidatingAccess(StartValidation);
}
}

View File

@ -68,6 +68,26 @@ class ValidatingDispatcher : public Dispatcher
public:
ValidatingDispatcher();
class MOZ_STACK_CLASS AutoProcessEvent final {
public:
AutoProcessEvent();
~AutoProcessEvent();
private:
ValidatingDispatcher* mPrevRunningDispatcher;
};
// Ensure that it's valid to access the TabGroup at this time.
void ValidateAccess() const
{
MOZ_ASSERT(!sRunningDispatcher || mAccessValid);
}
class Runnable;
friend class Runnable;
bool* GetValidAccessPtr() { return &mAccessValid; }
nsresult Dispatch(const char* aName,
TaskCategory aCategory,
already_AddRefed<nsIRunnable>&& aRunnable) override;
@ -87,9 +107,22 @@ protected:
// function returns |dispatcher|.
static ValidatingDispatcher* FromEventTarget(nsIEventTarget* aEventTarget);
nsresult LabeledDispatch(const char* aName,
TaskCategory aCategory,
already_AddRefed<nsIRunnable>&& aRunnable);
void CreateEventTargets(bool aNeedValidation);
void Shutdown();
enum ValidationType {
StartValidation,
EndValidation,
};
void SetValidatingAccess(ValidationType aType);
static ValidatingDispatcher* sRunningDispatcher;
bool mAccessValid;
nsCOMPtr<nsIEventTarget> mEventTargets[size_t(TaskCategory::Count)];
RefPtr<AbstractThread> mAbstractThreads[size_t(TaskCategory::Count)];
};

View File

@ -22,6 +22,7 @@
#include "nsQueryObject.h"
#include "pratom.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/Dispatcher.h"
#include "mozilla/Logging.h"
#include "nsIObserverService.h"
#include "mozilla/HangMonitor.h"
@ -1196,8 +1197,10 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
// and repeat the nested event loop since its state change hasn't happened yet.
bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown());
Maybe<ValidatingDispatcher::AutoProcessEvent> ape;
if (mIsMainThread == MAIN_THREAD) {
DoMainThreadSpecificProcessing(reallyWait);
ape.emplace();
}
++mNestedEventLoopDepth;