Bug 1060179 - Generate a random node id for every EME (origin,topLevelOrigin) pair. r=bz

This commit is contained in:
Chris Pearce 2014-10-09 17:01:02 +13:00
parent 70d390d0d8
commit 2c2b3ec19c
9 changed files with 292 additions and 66 deletions

View File

@ -545,6 +545,11 @@ public:
bool IsEventAttributeName(nsIAtom* aName) MOZ_OVERRIDE;
// Returns the principal of the "top level" document; the origin displayed
// in the URL bar of the browser window.
already_AddRefed<nsIPrincipal> GetTopLevelPrincipal();
#endif // MOZ_EME
bool MozAutoplayEnabled() const

View File

@ -3407,6 +3407,11 @@ void HTMLMediaElement::NotifyDecoderPrincipalChanged()
OutputMediaStream* ms = &mOutputStreams[i];
ms->mStream->CombineWithPrincipal(principal);
}
#ifdef MOZ_EME
if (mMediaKeys && NS_FAILED(mMediaKeys->CheckPrincipals())) {
mMediaKeys->Shutdown();
}
#endif
}
void HTMLMediaElement::UpdateMediaSize(nsIntSize size)
@ -4008,16 +4013,34 @@ HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
// TODO: Need to shutdown existing MediaKeys instance? bug 1016709.
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}
if (mMediaKeys != aMediaKeys) {
mMediaKeys = aMediaKeys;
if (mMediaKeys == aMediaKeys) {
promise->MaybeResolve(JS::UndefinedHandleValue);
return promise.forget();
}
if (mDecoder) {
mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
if (aMediaKeys && aMediaKeys->IsBoundToMediaElement()) {
promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
return promise.forget();
}
if (mMediaKeys) {
// Existing MediaKeys object. Shut it down.
mMediaKeys->Shutdown();
mMediaKeys = nullptr;
}
mMediaKeys = aMediaKeys;
if (mMediaKeys) {
if (NS_FAILED(mMediaKeys->Bind(this))) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
mMediaKeys = nullptr;
return promise.forget();
}
if (mDecoder) {
mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
}
}
promise->MaybeResolve(JS::UndefinedHandleValue);
return promise.forget();
@ -4063,6 +4086,28 @@ HTMLMediaElement::IsEventAttributeName(nsIAtom* aName)
return aName == nsGkAtoms::onencrypted ||
nsGenericHTMLElement::IsEventAttributeName(aName);
}
already_AddRefed<nsIPrincipal>
HTMLMediaElement::GetTopLevelPrincipal()
{
nsRefPtr<nsIPrincipal> principal;
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(OwnerDoc()->GetParentObject());
nsCOMPtr<nsIDOMWindow> topWindow;
if (!window) {
return nullptr;
}
window->GetTop(getter_AddRefs(topWindow));
nsCOMPtr<nsPIDOMWindow> top = do_QueryInterface(topWindow);
if (!top) {
return nullptr;
}
nsIDocument* doc = top->GetExtantDoc();
if (!doc) {
return nullptr;
}
principal = doc->NodePrincipal();
return principal.forget();
}
#endif // MOZ_EME
NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged()

View File

@ -36,17 +36,18 @@ CDMProxy::~CDMProxy()
}
void
CDMProxy::Init(PromiseId aPromiseId)
CDMProxy::Init(PromiseId aPromiseId,
const nsAString& aOrigin,
const nsAString& aTopLevelOrigin,
bool aInPrivateBrowsing)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
mNodeId = mKeys->GetNodeId();
if (mNodeId.IsEmpty()) {
RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
EME_LOG("Creating CDMProxy for origin='%s'", GetNodeId().get());
EME_LOG("CDMProxy::Init (%s, %s) %s",
NS_ConvertUTF16toUTF8(aOrigin).get(),
NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
(aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
if (!mGMPThread) {
nsCOMPtr<mozIGeckoMediaPluginService> mps =
@ -61,8 +62,15 @@ CDMProxy::Init(PromiseId aPromiseId)
return;
}
}
nsRefPtr<nsIRunnable> task(NS_NewRunnableMethodWithArg<uint32_t>(this, &CDMProxy::gmp_Init, aPromiseId));
nsAutoPtr<InitData> data(new InitData());
data->mPromiseId = aPromiseId;
data->mOrigin = aOrigin;
data->mTopLevelOrigin = aTopLevelOrigin;
data->mInPrivateBrowsing = aInPrivateBrowsing;
nsRefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<nsAutoPtr<InitData>>(this,
&CDMProxy::gmp_Init,
data));
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
@ -75,26 +83,45 @@ CDMProxy::IsOnGMPThread()
#endif
void
CDMProxy::gmp_Init(uint32_t aPromiseId)
CDMProxy::gmp_Init(nsAutoPtr<InitData> aData)
{
MOZ_ASSERT(IsOnGMPThread());
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
if (!mps) {
RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
nsresult rv = mps->GetNodeId(aData->mOrigin,
aData->mTopLevelOrigin,
aData->mInPrivateBrowsing,
mNodeId);
MOZ_ASSERT(!GetNodeId().IsEmpty());
if (NS_FAILED(rv)) {
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
EME_LOG("CDMProxy::gmp_Init (%s, %s) %s NodeId=%s",
NS_ConvertUTF16toUTF8(aData->mOrigin).get(),
NS_ConvertUTF16toUTF8(aData->mTopLevelOrigin).get(),
(aData->mInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"),
GetNodeId().get());
nsTArray<nsCString> tags;
tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
nsresult rv = mps->GetGMPDecryptor(&tags, GetNodeId(), &mCDM);
rv = mps->GetGMPDecryptor(&tags, GetNodeId(), &mCDM);
if (NS_FAILED(rv) || !mCDM) {
RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
} else {
mCallback = new CDMCallbackProxy(this);
mCDM->Init(mCallback);
nsRefPtr<nsIRunnable> task(NS_NewRunnableMethodWithArg<uint32_t>(this, &CDMProxy::OnCDMCreated, aPromiseId));
nsRefPtr<nsIRunnable> task(
NS_NewRunnableMethodWithArg<uint32_t>(this,
&CDMProxy::OnCDMCreated,
aData->mPromiseId));
NS_DispatchToMainThread(task);
}
}
@ -106,7 +133,8 @@ CDMProxy::OnCDMCreated(uint32_t aPromiseId)
if (mKeys.IsNull()) {
return;
}
mKeys->OnCDMCreated(aPromiseId);
MOZ_ASSERT(!GetNodeId().IsEmpty());
mKeys->OnCDMCreated(aPromiseId, GetNodeId());
}
void
@ -303,9 +331,7 @@ CDMProxy::Shutdown()
mKeys.Clear();
// Note: This may end up being the last owning reference to the CDMProxy.
nsRefPtr<nsIRunnable> task(NS_NewRunnableMethod(this, &CDMProxy::gmp_Shutdown));
if (mGMPThread) {
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
void

View File

@ -48,7 +48,10 @@ public:
// Main thread only.
// Loads the CDM corresponding to mKeySystem.
// Calls MediaKeys::OnCDMCreated() when the CDM is created.
void Init(PromiseId aPromiseId);
void Init(PromiseId aPromiseId,
const nsAString& aOrigin,
const nsAString& aTopLevelOrigin,
bool aInPrivateBrowsing);
// Main thread only.
// Uses the CDM to create a key session.
@ -165,8 +168,15 @@ public:
private:
struct InitData {
uint32_t mPromiseId;
nsAutoString mOrigin;
nsAutoString mTopLevelOrigin;
bool mInPrivateBrowsing;
};
// GMP thread only.
void gmp_Init(uint32_t aPromiseId);
void gmp_Init(nsAutoPtr<InitData> aData);
// GMP thread only.
void gmp_Shutdown();

View File

@ -25,6 +25,7 @@ namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeys,
mElement,
mParent,
mKeySessions,
mPromises,
@ -224,23 +225,85 @@ MediaKeys::Create(const GlobalObject& aGlobal,
// CDMProxy keeps MediaKeys alive until it resolves the promise and thus
// returns the MediaKeys object to JS.
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
if (!window || !window->GetExtantDoc()) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<MediaKeys> keys = new MediaKeys(window, aKeySystem);
nsRefPtr<Promise> promise(keys->MakePromise(aRv));
return keys->Init(aRv);
}
already_AddRefed<Promise>
MediaKeys::Init(ErrorResult& aRv)
{
nsRefPtr<Promise> promise(MakePromise(aRv));
if (aRv.Failed()) {
return nullptr;
}
if (!IsSupportedKeySystem(aKeySystem)) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
if (!IsSupportedKeySystem(mKeySystem)) {
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return promise.forget();
}
keys->mProxy = new CDMProxy(keys, aKeySystem);
mProxy = new CDMProxy(this, mKeySystem);
// Determine principal (at creation time) of the MediaKeys object.
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject());
if (!sop) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
mPrincipal = sop->GetPrincipal();
// Determine principal of the "top-level" window; the principal of the
// page that will display in the URL bar.
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetParentObject());
if (!window) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
nsCOMPtr<nsIDOMWindow> topWindow;
window->GetTop(getter_AddRefs(topWindow));
nsCOMPtr<nsPIDOMWindow> top = do_QueryInterface(topWindow);
if (!top || !top->GetExtantDoc()) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
mTopLevelPrincipal = top->GetExtantDoc()->NodePrincipal();
if (!mPrincipal || !mTopLevelPrincipal) {
NS_WARNING("Failed to get principals when creating MediaKeys");
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
nsAutoString origin;
nsresult rv = nsContentUtils::GetUTFOrigin(mPrincipal, origin);
if (NS_FAILED(rv)) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
nsAutoString topLevelOrigin;
rv = nsContentUtils::GetUTFOrigin(mTopLevelPrincipal, topLevelOrigin);
if (NS_FAILED(rv)) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
if (!window) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
nsIDocument* doc = window->GetExtantDoc();
const bool inPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(doc);
EME_LOG("MediaKeys::Create() (%s, %s), %s",
NS_ConvertUTF16toUTF8(origin).get(),
NS_ConvertUTF16toUTF8(topLevelOrigin).get(),
(inPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
// The CDMProxy's initialization is asynchronous. The MediaKeys is
// refcounted, and its instance is returned to JS by promise once
@ -250,22 +313,26 @@ MediaKeys::Create(const GlobalObject& aGlobal,
// or its creation has failed. Store the id of the promise returned
// here, and hold a self-reference until that promise is resolved or
// rejected.
MOZ_ASSERT(!keys->mCreatePromiseId, "Should only be created once!");
keys->mCreatePromiseId = keys->StorePromise(promise);
keys->AddRef();
keys->mProxy->Init(keys->mCreatePromiseId);
MOZ_ASSERT(!mCreatePromiseId, "Should only be created once!");
mCreatePromiseId = StorePromise(promise);
AddRef();
mProxy->Init(mCreatePromiseId,
origin,
topLevelOrigin,
inPrivateBrowsing);
return promise.forget();
}
void
MediaKeys::OnCDMCreated(PromiseId aId)
MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId)
{
nsRefPtr<Promise> promise(RetrievePromise(aId));
if (!promise) {
NS_WARNING("MediaKeys tried to resolve a non-existent promise");
return;
}
mNodeId = aNodeId;
nsRefPtr<MediaKeys> keys(this);
promise->MaybeResolve(keys);
if (mCreatePromiseId == aId) {
@ -367,32 +434,59 @@ MediaKeys::GetSession(const nsAString& aSessionId)
}
const nsCString&
MediaKeys::GetNodeId()
MediaKeys::GetNodeId() const
{
MOZ_ASSERT(NS_IsMainThread());
// TODO: Bug 1035637, return a combination of origin and URL bar origin.
if (!mNodeId.IsEmpty()) {
return mNodeId;
}
nsIPrincipal* principal = nullptr;
nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(GetParentObject());
nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
do_QueryInterface(pWindow);
if (scriptPrincipal) {
principal = scriptPrincipal->GetPrincipal();
}
nsAutoString id;
if (principal && NS_SUCCEEDED(nsContentUtils::GetUTFOrigin(principal, id))) {
CopyUTF16toUTF8(id, mNodeId);
EME_LOG("EME Origin = '%s'", mNodeId.get());
}
return mNodeId;
}
bool
MediaKeys::IsBoundToMediaElement() const
{
MOZ_ASSERT(NS_IsMainThread());
return mElement != nullptr;
}
nsresult
MediaKeys::Bind(HTMLMediaElement* aElement)
{
MOZ_ASSERT(NS_IsMainThread());
if (IsBoundToMediaElement()) {
return NS_ERROR_FAILURE;
}
mElement = aElement;
nsresult rv = CheckPrincipals();
if (NS_FAILED(rv)) {
mElement = nullptr;
return rv;
}
return NS_OK;
}
nsresult
MediaKeys::CheckPrincipals()
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsBoundToMediaElement()) {
return NS_ERROR_FAILURE;
}
nsRefPtr<nsIPrincipal> elementPrincipal(mElement->GetCurrentPrincipal());
nsRefPtr<nsIPrincipal> elementTopLevelPrincipal(mElement->GetTopLevelPrincipal());
if (!elementPrincipal ||
!mPrincipal ||
!elementPrincipal->Equals(mPrincipal) ||
!elementTopLevelPrincipal ||
!mTopLevelPrincipal ||
!elementTopLevelPrincipal->Equals(mTopLevelPrincipal)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
bool
CopyArrayBufferViewOrArrayBufferData(const ArrayBufferViewOrArrayBuffer& aBufferOrView,
nsTArray<uint8_t>& aOutData)

View File

@ -18,6 +18,7 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/MediaKeysBinding.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozIGeckoMediaPluginService.h"
namespace mozilla {
@ -26,6 +27,7 @@ class CDMProxy;
namespace dom {
class MediaKeySession;
class HTMLMediaElement;
typedef nsRefPtrHashtable<nsStringHashKey, MediaKeySession> KeySessionHashMap;
typedef nsRefPtrHashtable<nsUint32HashKey, dom::Promise> PromiseHashMap;
@ -55,6 +57,8 @@ public:
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
nsresult Bind(HTMLMediaElement* aElement);
// Javascript: readonly attribute DOMString keySystem;
void GetKeySystem(nsString& retval) const;
@ -82,7 +86,7 @@ public:
already_AddRefed<MediaKeySession> GetSession(const nsAString& aSessionId);
// Called once a Create() operation succeeds.
void OnCDMCreated(PromiseId aId);
void OnCDMCreated(PromiseId aId, const nsACString& aNodeId);
// Called when GenerateRequest or Load have been called on a MediaKeySession
// and we are waiting for its initialisation to finish.
void OnSessionPending(PromiseId aId, MediaKeySession* aSession);
@ -107,12 +111,22 @@ public:
// Resolves promise with "undefined".
void ResolvePromise(PromiseId aId);
const nsCString& GetNodeId();
const nsCString& GetNodeId() const;
void Shutdown();
// Returns true if this MediaKeys has been bound to a media element.
bool IsBoundToMediaElement() const;
// Return NS_OK if the principals are the same as when the MediaKeys
// was created, failure otherwise.
nsresult CheckPrincipals();
private:
bool IsInPrivateBrowsing();
already_AddRefed<Promise> Init(ErrorResult& aRv);
// Removes promise from mPromises, and returns it.
already_AddRefed<Promise> RetrievePromise(PromiseId aId);
@ -120,6 +134,8 @@ private:
// and the MediaKeys destructor clears the proxy's reference to the MediaKeys.
nsRefPtr<CDMProxy> mProxy;
nsRefPtr<HTMLMediaElement> mElement;
nsCOMPtr<nsPIDOMWindow> mParent;
nsString mKeySystem;
nsCString mNodeId;
@ -127,6 +143,10 @@ private:
PromiseHashMap mPromises;
PendingKeySessionsHashMap mPendingSessions;
PromiseId mCreatePromiseId;
nsRefPtr<nsIPrincipal> mPrincipal;
nsRefPtr<nsIPrincipal> mTopLevelPrincipal;
};
} // namespace dom

View File

@ -22,6 +22,7 @@
#include "nsComponentManagerUtils.h"
#include "mozilla/Preferences.h"
#include "runnable_utils.h"
#include "VideoUtils.h"
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
#include "mozilla/Sandbox.h"
#endif
@ -854,5 +855,28 @@ GeckoMediaPluginService::ReAddOnGMPThread(nsRefPtr<GMPParent>& aOld)
NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld));
}
NS_IMETHODIMP
GeckoMediaPluginService::GetNodeId(const nsAString& aOrigin,
const nsAString& aTopLevelOrigin,
bool aInPrivateBrowsing,
nsACString& aOutId)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
LOGD(("%s::%s: (%s, %s), %s", __CLASS__, __FUNCTION__,
NS_ConvertUTF16toUTF8(aOrigin).get(),
NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
(aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing")));
nsAutoCString salt;
nsresult rv = GenerateRandomPathName(salt, 32);
NS_ENSURE_SUCCESS(rv, rv);
aOutId = salt;
// TODO: Store salt, so it can be retrieved in subsequent sessions.
return NS_OK;
}
} // namespace gmp
} // namespace mozilla

View File

@ -78,12 +78,7 @@ GetGMPStorageDir(nsIFile** aTempDir, const nsCString& aNodeId)
return rv;
}
// TODO: When aOrigin is the same node-id as the GMP sees in the child
// process (a UUID or somesuch), we can just append it un-hashed here.
// This should reduce the chance of hash collsions exposing data.
nsAutoString nodeIdHash;
nodeIdHash.AppendInt(HashString(aNodeId));
rv = tmpFile->Append(nodeIdHash);
rv = tmpFile->AppendNative(aNodeId);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

View File

@ -25,7 +25,7 @@ class GMPVideoHost;
[ptr] native GMPDecryptorProxy(GMPDecryptorProxy);
[ptr] native GMPAudioDecoderProxy(GMPAudioDecoderProxy);
[scriptable, uuid(88ade941-a423-48f9-aa3d-a383af8de4b8)]
[scriptable, uuid(3d811f9f-e1f8-48a5-a385-3657a641ee76)]
interface mozIGeckoMediaPluginService : nsISupports
{
@ -89,4 +89,11 @@ interface mozIGeckoMediaPluginService : nsISupports
* @note Main-thread API.
*/
void removePluginDirectory(in AString directory);
/**
* Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
*/
ACString getNodeId(in AString origin,
in AString topLevelOrigin,
in bool inPrivateBrowsingMode);
};