Bug 1060179 - Ensure GMP storage respects private browsing mode. r=ehsan,jesup

This commit is contained in:
Chris Pearce 2014-10-13 11:53:44 +13:00
parent e7a9447873
commit 387eac6df8
6 changed files with 322 additions and 84 deletions

View File

@ -711,8 +711,12 @@ GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor)
}
bool
GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* actor)
GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* aActor)
{
GMPStorageParent* p = (GMPStorageParent*)aActor;
if (NS_WARN_IF(NS_FAILED(p->Init()))) {
return false;
}
return true;
}

View File

@ -170,6 +170,7 @@ GeckoMediaPluginService::Init()
MOZ_ASSERT(obsService);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "profile-change-teardown", false)));
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)));
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "last-pb-context-exited", false)));
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
@ -312,6 +313,13 @@ GeckoMediaPluginService::Observe(nsISupports* aSubject,
if (gmpThread) {
gmpThread->Shutdown();
}
} else if (!strcmp("last-pb-context-exited", aTopic)) {
// When Private Browsing mode exits, all we need to do is clear
// mTempNodeIds. This drops all the node ids we've cached in memory
// for PB origin-pairs. If we try to open an origin-pair for non-PB
// mode, we'll get the NodeId salt stored on-disk, and if we try to
// open a PB mode origin-pair, we'll re-generate new salt.
mTempNodeIds.Clear();
}
return NS_OK;
}
@ -958,6 +966,16 @@ ReadFromFile(nsIFile* aPath,
return NS_OK;
}
NS_IMETHODIMP
GeckoMediaPluginService::IsPersistentStorageAllowed(const nsACString& aNodeId,
bool* aOutAllowed)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
NS_ENSURE_ARG(aOutAllowed);
*aOutAllowed = mPersistentStorageAllowed.Get(aNodeId);
return NS_OK;
}
NS_IMETHODIMP
GeckoMediaPluginService::GetNodeId(const nsAString& aOrigin,
const nsAString& aTopLevelOrigin,
@ -973,16 +991,44 @@ GeckoMediaPluginService::GetNodeId(const nsAString& aOrigin,
nsresult rv;
const uint32_t NodeIdSaltLength = 32;
if (aInPrivateBrowsing ||
aOrigin.EqualsLiteral("null") ||
if (aOrigin.EqualsLiteral("null") ||
aOrigin.IsEmpty() ||
aTopLevelOrigin.EqualsLiteral("null") ||
aTopLevelOrigin.IsEmpty()) {
// Non-persistent session; just generate a random node id.
// At least one of the (origin, topLevelOrigin) is null or empty;
// probably a local file. Generate a random node id, and don't store
// it so that the GMP's storage is temporary and not shared.
nsAutoCString salt;
rv = GenerateRandomPathName(salt, NodeIdSaltLength);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aOutId = salt;
return rv;
mPersistentStorageAllowed.Put(salt, false);
return NS_OK;
}
const uint32_t hash = AddToHash(HashString(aOrigin),
HashString(aTopLevelOrigin));
if (aInPrivateBrowsing) {
// For PB mode, we store the node id, indexed by the origin pair,
// so that if the same origin pair is opened in this session, it gets
// the same node id.
nsCString* salt = nullptr;
if (!(salt = mTempNodeIds.Get(hash))) {
// No salt stored, generate and temporarily store some for this id.
nsAutoCString newSalt;
rv = GenerateRandomPathName(newSalt, NodeIdSaltLength);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
salt = new nsCString(newSalt);
mTempNodeIds.Put(hash, salt);
mPersistentStorageAllowed.Put(*salt, false);
}
aOutId = *salt;
return NS_OK;
}
// Otherwise, try to see if we've previously generated and stored salt
@ -1004,8 +1050,6 @@ GeckoMediaPluginService::GetNodeId(const nsAString& aOrigin,
return rv;
}
uint32_t hash = AddToHash(HashString(aOrigin),
HashString(aTopLevelOrigin));
nsAutoCString hashStr;
hashStr.AppendInt((int64_t)hash);
@ -1079,6 +1123,7 @@ GeckoMediaPluginService::GetNodeId(const nsAString& aOrigin,
}
aOutId = salt;
mPersistentStorageAllowed.Put(salt, true);
return NS_OK;
}

View File

@ -17,6 +17,8 @@
#include "nsIThread.h"
#include "nsThreadUtils.h"
#include "nsITimer.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
template <class> struct already_AddRefed;
@ -113,6 +115,14 @@ private:
nsCOMPtr<nsITimer> mAsyncShutdownTimeout; // GMP Thread only.
nsCOMPtr<nsIFile> mStorageBaseDir;
// Hashes of (origin,topLevelOrigin) to the node id for
// non-persistent sessions.
nsClassHashtable<nsUint32HashKey, nsCString> mTempNodeIds;
// Hashes node id to whether that node id is allowed to store data
// persistently on disk.
nsDataHashtable<nsCStringHashKey, bool> mPersistentStorageAllowed;
};
} // namespace gmp

View File

@ -12,6 +12,12 @@
#include "GMPParent.h"
#include "gmp-storage.h"
#include "mozilla/unused.h"
#include "nsTHashtable.h"
#include "nsDataHashtable.h"
#include "prio.h"
#include "mozIGeckoMediaPluginService.h"
#include "nsContentCID.h"
#include "nsServiceManagerUtils.h"
namespace mozilla {
@ -82,14 +88,6 @@ GetGMPStorageDir(nsIFile** aTempDir, const nsCString& aNodeId)
return NS_OK;
}
GMPStorageParent::GMPStorageParent(const nsCString& aNodeId,
GMPParent* aPlugin)
: mNodeId(aNodeId)
, mPlugin(aPlugin)
, mShutdown(false)
{
}
enum OpenFileMode { ReadWrite, Truncate };
nsresult
@ -118,11 +116,208 @@ OpenStorageFile(const nsCString& aRecordName,
return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
}
PLDHashOperator
CloseFile(const nsACString& key, PRFileDesc*& entry, void* cx)
{
if (PR_Close(entry) != PR_SUCCESS) {
NS_WARNING("GMPDiskStorage Failed to clsose file.");
}
return PL_DHASH_REMOVE;
}
class GMPDiskStorage : public GMPStorage {
public:
GMPDiskStorage(const nsCString& aNodeId)
: mNodeId(aNodeId)
{
}
~GMPDiskStorage() {
mFiles.Enumerate(CloseFile, nullptr);
MOZ_ASSERT(!mFiles.Count());
}
virtual GMPErr Open(const nsCString& aRecordName) MOZ_OVERRIDE
{
MOZ_ASSERT(!IsOpen(aRecordName));
PRFileDesc* fd = nullptr;
if (NS_FAILED(OpenStorageFile(aRecordName, mNodeId, ReadWrite, &fd))) {
NS_WARNING("Failed to open storage file.");
return GMPGenericErr;
}
mFiles.Put(aRecordName, fd);
return GMPNoErr;
}
virtual bool IsOpen(const nsCString& aRecordName) MOZ_OVERRIDE {
return mFiles.Contains(aRecordName);
}
virtual GMPErr Read(const nsCString& aRecordName,
nsTArray<uint8_t>& aOutBytes) MOZ_OVERRIDE
{
PRFileDesc* fd = mFiles.Get(aRecordName);
if (!fd) {
return GMPGenericErr;
}
int32_t len = PR_Seek(fd, 0, PR_SEEK_END);
PR_Seek(fd, 0, PR_SEEK_SET);
if (len > GMP_MAX_RECORD_SIZE) {
// Refuse to read big records.
return GMPQuotaExceededErr;
}
aOutBytes.SetLength(len);
auto bytesRead = PR_Read(fd, aOutBytes.Elements(), len);
return (bytesRead == len) ? GMPNoErr : GMPGenericErr;
}
virtual GMPErr Write(const nsCString& aRecordName,
const nsTArray<uint8_t>& aBytes) MOZ_OVERRIDE
{
PRFileDesc* fd = mFiles.Get(aRecordName);
if (!fd) {
return GMPGenericErr;
}
// Write operations overwrite the entire record. So re-open the file
// in truncate mode, to clear its contents.
PR_Close(fd);
mFiles.Remove(aRecordName);
if (NS_FAILED(OpenStorageFile(aRecordName, mNodeId, Truncate, &fd))) {
return GMPGenericErr;
}
mFiles.Put(aRecordName, fd);
int32_t bytesWritten = PR_Write(fd, aBytes.Elements(), aBytes.Length());
return (bytesWritten == (int32_t)aBytes.Length()) ? GMPNoErr : GMPGenericErr;
}
virtual void Close(const nsCString& aRecordName) MOZ_OVERRIDE
{
PRFileDesc* fd = mFiles.Get(aRecordName);
if (fd) {
if (PR_Close(fd) == PR_SUCCESS) {
mFiles.Remove(aRecordName);
} else {
NS_WARNING("GMPDiskStorage Failed to clsose file.");
}
}
}
private:
nsDataHashtable<nsCStringHashKey, PRFileDesc*> mFiles;
const nsAutoCString mNodeId;
};
class GMPMemoryStorage : public GMPStorage {
public:
virtual GMPErr Open(const nsCString& aRecordName) MOZ_OVERRIDE
{
MOZ_ASSERT(!IsOpen(aRecordName));
Record* record = nullptr;
if (!mRecords.Get(aRecordName, &record)) {
record = new Record();
mRecords.Put(aRecordName, record);
}
record->mIsOpen = true;
return GMPNoErr;
}
virtual bool IsOpen(const nsCString& aRecordName) MOZ_OVERRIDE {
Record* record = nullptr;
if (!mRecords.Get(aRecordName, &record)) {
return false;
}
return record->mIsOpen;
}
virtual GMPErr Read(const nsCString& aRecordName,
nsTArray<uint8_t>& aOutBytes) MOZ_OVERRIDE
{
Record* record = nullptr;
if (!mRecords.Get(aRecordName, &record)) {
return GMPGenericErr;
}
aOutBytes = record->mData;
return GMPNoErr;
}
virtual GMPErr Write(const nsCString& aRecordName,
const nsTArray<uint8_t>& aBytes) MOZ_OVERRIDE
{
Record* record = nullptr;
if (!mRecords.Get(aRecordName, &record)) {
return GMPClosedErr;
}
record->mData = aBytes;
return GMPNoErr;
}
virtual void Close(const nsCString& aRecordName) MOZ_OVERRIDE
{
Record* record = nullptr;
if (!mRecords.Get(aRecordName, &record)) {
return;
}
if (!record->mData.Length()) {
// Record is empty, delete.
mRecords.Remove(aRecordName);
} else {
record->mIsOpen = false;
}
}
private:
struct Record {
Record() : mIsOpen(false) {}
nsTArray<uint8_t> mData;
bool mIsOpen;
};
nsClassHashtable<nsCStringHashKey, Record> mRecords;
};
GMPStorageParent::GMPStorageParent(const nsCString& aNodeId,
GMPParent* aPlugin)
: mNodeId(aNodeId)
, mPlugin(aPlugin)
, mShutdown(false)
{
}
nsresult
GMPStorageParent::Init()
{
if (NS_WARN_IF(mNodeId.IsEmpty())) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
if (NS_WARN_IF(!mps)) {
return NS_ERROR_FAILURE;
}
bool persistent = false;
if (NS_WARN_IF(NS_FAILED(mps->IsPersistentStorageAllowed(mNodeId, &persistent)))) {
return NS_ERROR_FAILURE;
}
if (persistent) {
mStorage = MakeUnique<GMPDiskStorage>(mNodeId);
} else {
mStorage = MakeUnique<GMPMemoryStorage>();
}
return NS_OK;
}
bool
GMPStorageParent::RecvOpen(const nsCString& aRecordName)
{
if (mShutdown) {
return true;
return false;
}
if (mNodeId.EqualsLiteral("null")) {
@ -133,21 +328,19 @@ GMPStorageParent::RecvOpen(const nsCString& aRecordName)
return true;
}
if (aRecordName.IsEmpty() || mFiles.Contains(aRecordName)) {
unused << SendOpenComplete(aRecordName, GMPRecordInUse);
return true;
}
PRFileDesc* fd = nullptr;
nsresult rv = OpenStorageFile(aRecordName, mNodeId, ReadWrite, &fd);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to open storage file.");
if (aRecordName.IsEmpty()) {
unused << SendOpenComplete(aRecordName, GMPGenericErr);
return true;
}
mFiles.Put(aRecordName, fd);
unused << SendOpenComplete(aRecordName, GMPNoErr);
if (mStorage->IsOpen(aRecordName)) {
unused << SendOpenComplete(aRecordName, GMPRecordInUse);
return true;
}
auto err = mStorage->Open(aRecordName);
MOZ_ASSERT(GMP_FAILED(err) || mStorage->IsOpen(aRecordName));
unused << SendOpenComplete(aRecordName, err);
return true;
}
@ -158,29 +351,16 @@ GMPStorageParent::RecvRead(const nsCString& aRecordName)
LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
if (mShutdown) {
return true;
return false;
}
PRFileDesc* fd = mFiles.Get(aRecordName);
nsTArray<uint8_t> data;
if (!fd) {
if (!mStorage->IsOpen(aRecordName)) {
unused << SendReadComplete(aRecordName, GMPClosedErr, data);
return true;
} else {
unused << SendReadComplete(aRecordName, mStorage->Read(aRecordName, data), data);
}
int32_t len = PR_Seek(fd, 0, PR_SEEK_END);
PR_Seek(fd, 0, PR_SEEK_SET);
if (len > GMP_MAX_RECORD_SIZE) {
// Refuse to read big records.
unused << SendReadComplete(aRecordName, GMPQuotaExceededErr, data);
return true;
}
data.SetLength(len);
auto bytesRead = PR_Read(fd, data.Elements(), len);
auto res = (bytesRead == len) ? GMPNoErr : GMPGenericErr;
unused << SendReadComplete(aRecordName, res, data);
return true;
}
@ -191,32 +371,21 @@ GMPStorageParent::RecvWrite(const nsCString& aRecordName,
LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
if (mShutdown) {
return false;
}
if (!mStorage->IsOpen(aRecordName)) {
unused << SendWriteComplete(aRecordName, GMPClosedErr);
return true;
}
if (aBytes.Length() > GMP_MAX_RECORD_SIZE) {
unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr);
return true;
}
PRFileDesc* fd = mFiles.Get(aRecordName);
if (!fd) {
unused << SendWriteComplete(aRecordName, GMPGenericErr);
return true;
}
unused << SendWriteComplete(aRecordName, mStorage->Write(aRecordName, aBytes));
// Write operations overwrite the entire record. So re-open the file
// in truncate mode, to clear its contents.
PR_Close(fd);
mFiles.Remove(aRecordName);
if (NS_FAILED(OpenStorageFile(aRecordName, mNodeId, Truncate, &fd))) {
unused << SendWriteComplete(aRecordName, GMPGenericErr);
return true;
}
mFiles.Put(aRecordName, fd);
int32_t bytesWritten = PR_Write(fd, aBytes.Elements(), aBytes.Length());
auto res = (bytesWritten == (int32_t)aBytes.Length()) ? GMPNoErr : GMPGenericErr;
unused << SendWriteComplete(aRecordName, res);
return true;
}
@ -229,12 +398,8 @@ GMPStorageParent::RecvClose(const nsCString& aRecordName)
return true;
}
PRFileDesc* fd = mFiles.Get(aRecordName);
if (!fd) {
return true;
}
PR_Close(fd);
mFiles.Remove(aRecordName);
mStorage->Close(aRecordName);
return true;
}
@ -245,13 +410,6 @@ GMPStorageParent::ActorDestroy(ActorDestroyReason aWhy)
Shutdown();
}
PLDHashOperator
CloseFile(const nsACString& key, PRFileDesc*& entry, void* cx)
{
PR_Close(entry);
return PL_DHASH_REMOVE;
}
void
GMPStorageParent::Shutdown()
{
@ -263,8 +421,8 @@ GMPStorageParent::Shutdown()
mShutdown = true;
unused << SendShutdown();
mFiles.Enumerate(CloseFile, nullptr);
MOZ_ASSERT(!mFiles.Count());
mStorage = nullptr;
}
} // namespace gmp

View File

@ -8,20 +8,33 @@
#include "mozilla/gmp/PGMPStorageParent.h"
#include "gmp-storage.h"
#include "nsTHashtable.h"
#include "nsDataHashtable.h"
#include "prio.h"
#include "mozilla/UniquePtr.h"
namespace mozilla {
namespace gmp {
class GMPParent;
class GMPStorage {
public:
virtual ~GMPStorage() {}
virtual GMPErr Open(const nsCString& aRecordName) = 0;
virtual bool IsOpen(const nsCString& aRecordName) = 0;
virtual GMPErr Read(const nsCString& aRecordName,
nsTArray<uint8_t>& aOutBytes) = 0;
virtual GMPErr Write(const nsCString& aRecordName,
const nsTArray<uint8_t>& aBytes) = 0;
virtual void Close(const nsCString& aRecordName) = 0;
};
class GMPStorageParent : public PGMPStorageParent {
public:
NS_INLINE_DECL_REFCOUNTING(GMPStorageParent)
GMPStorageParent(const nsCString& aNodeId, GMPParent* aPlugin);
GMPStorageParent(const nsCString& aNodeId,
GMPParent* aPlugin);
nsresult Init();
void Shutdown();
protected:
@ -35,8 +48,9 @@ protected:
private:
~GMPStorageParent() {}
nsDataHashtable<nsCStringHashKey, PRFileDesc*> mFiles;
const nsAutoCString mNodeId;
UniquePtr<GMPStorage> mStorage;
const nsCString mNodeId;
nsRefPtr<GMPParent> mPlugin;
bool mShutdown;
};

View File

@ -26,7 +26,7 @@ class GMPVideoHost;
[ptr] native GMPDecryptorProxy(GMPDecryptorProxy);
[ptr] native GMPAudioDecoderProxy(GMPAudioDecoderProxy);
[scriptable, uuid(e5cde76d-f926-4b3f-84ff-62864c7a750a)]
[scriptable, uuid(b350d3b6-00c9-4602-bdfe-84c2be8d1e7a)]
interface mozIGeckoMediaPluginService : nsISupports
{
@ -98,6 +98,13 @@ interface mozIGeckoMediaPluginService : nsISupports
in AString topLevelOrigin,
in bool inPrivateBrowsingMode);
/**
* Returns true if the given node id is allowed to store things
* persistently on disk. Private Browsing and local content are not
* allowed to store persistent data.
*/
bool isPersistentStorageAllowed(in ACString nodeId);
/**
* Returns the directory to use as the base for storing data about GMPs.
*/