Skip to content

Commit

Permalink
Bug 1784966: Encrypt IDB files and data in PBM, r=dom-storage-reviewe…
Browse files Browse the repository at this point in the history
…rs,jari,webdriver-reviewers,whimboo

Differential Revision: https://phabricator.services.mozilla.com/D158597
  • Loading branch information
hsingh-moz committed Oct 24, 2022
1 parent 5a1f625 commit 874e0b7
Show file tree
Hide file tree
Showing 10 changed files with 362 additions and 82 deletions.
183 changes: 111 additions & 72 deletions dom/indexedDB/ActorsParent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "IndexedDBCommon.h"
#include "IndexedDatabaseInlines.h"
#include "IndexedDatabaseManager.h"
#include "IndexedDBCipherKeyManager.h"
#include "KeyPath.h"
#include "MainThreadUtils.h"
#include "ProfilerHelpers.h"
Expand Down Expand Up @@ -2384,7 +2385,6 @@ class Database final

bool IsInPrivateBrowsing() const {
AssertIsOnBackgroundThread();

return mInPrivateBrowsing;
}

Expand Down Expand Up @@ -3750,6 +3750,47 @@ class NormalTransactionOp : public TransactionDatabaseOperationBase,
const PreprocessResponse& aResponse) final;
};

Maybe<CipherKey> IndexedDBCipherKeyManager::Get(const nsCString& aDatabaseID,
const nsCString& keyStoreID) {
auto lockedPrivateBrowsingInfoHashTable =
mPrivateBrowsingInfoHashTable.Lock();

auto dbKeyStore = lockedPrivateBrowsingInfoHashTable->Lookup(aDatabaseID);
if (!dbKeyStore) {
return Nothing();
}

return dbKeyStore->MaybeGet(keyStoreID);
}

CipherKey IndexedDBCipherKeyManager::Ensure(const nsCString& aDatabaseID,
const nsCString& keyStoreID) {
auto lockedPrivateBrowsingInfoHashTable =
mPrivateBrowsingInfoHashTable.Lock();

auto& dbKeyStore =
lockedPrivateBrowsingInfoHashTable->LookupOrInsert(aDatabaseID);
return dbKeyStore.LookupOrInsertWith(keyStoreID, [] {
// XXX Generate key using proper random data, such that we can ensure
// the use of unique IVs per key by discriminating by database's file
// id & offset.
auto keyOrErr = IndexedDBCipherStrategy::GenerateKey();

// XXX Propagate the error to the caller rather than asserting.
return keyOrErr.unwrap();
});
}

bool IndexedDBCipherKeyManager::Remove(const nsCString& aDatabaseID) {
auto lockedPrivateBrowsingInfoHashTable =
mPrivateBrowsingInfoHashTable.Lock();
return lockedPrivateBrowsingInfoHashTable->Remove(aDatabaseID);
}

// XXX Maybe we can avoid a mutex here by moving all accesses to the background
// thread.
StaticAutoPtr<IndexedDBCipherKeyManager> gIndexedDBCipherKeyManager;

class ObjectStoreAddOrPutRequestOp final : public NormalTransactionOp {
friend class TransactionBase;

Expand All @@ -3765,7 +3806,7 @@ class ObjectStoreAddOrPutRequestOp final : public NormalTransactionOp {
#ifdef DEBUG
const StructuredCloneFileBase::FileType mType;
#endif

void EnsureCipherKey();
void AssertInvariants() const;

explicit StoredFileInfo(SafeRefPtr<DatabaseFileInfo> aFileInfo);
Expand Down Expand Up @@ -3899,6 +3940,18 @@ void ObjectStoreAddOrPutRequestOp::StoredFileInfo::AssertInvariants() const {
}
}

void ObjectStoreAddOrPutRequestOp::StoredFileInfo::EnsureCipherKey() {
const auto& fileInfo = GetFileInfo();
const auto& fileMgr = fileInfo.Manager();

// no need to generate cipher keys if we are not in PBM
if (!fileMgr.IsInPrivateBrowsingMode()) return;

nsCString keyId;
keyId.AppendInt(fileInfo.Id());
gIndexedDBCipherKeyManager->Ensure(fileMgr.DatabaseID(), keyId);
}

ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
SafeRefPtr<DatabaseFileInfo> aFileInfo)
: mFileInfo{WrapNotNull(std::move(aFileInfo))},
Expand All @@ -3911,6 +3964,7 @@ ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
AssertIsOnBackgroundThread();
AssertInvariants();

EnsureCipherKey();
MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
}

Expand All @@ -3926,6 +3980,7 @@ ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
AssertIsOnBackgroundThread();
AssertInvariants();

EnsureCipherKey();
MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
}

Expand All @@ -3942,6 +3997,7 @@ ObjectStoreAddOrPutRequestOp::StoredFileInfo::StoredFileInfo(
AssertIsOnBackgroundThread();
AssertInvariants();

EnsureCipherKey();
MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
}

Expand Down Expand Up @@ -5555,19 +5611,26 @@ class EncryptedFileBlobImpl final : public FileBlobImpl {
}

private:
const CipherKey& mKey;
const CipherKey mKey;
};

RefPtr<BlobImpl> CreateFileBlobImpl(const Database& aDatabase,
const nsCOMPtr<nsIFile>& aNativeFile,
const DatabaseFileInfo::IdType aId) {
const auto& maybeKey = aDatabase.MaybeKeyRef();
if (maybeKey) {
return MakeRefPtr<EncryptedFileBlobImpl>(aNativeFile, aId, *maybeKey);
if (aDatabase.IsInPrivateBrowsing()) {
nsCString cipherKeyId;
cipherKeyId.AppendInt(aId);

const auto& key =
gIndexedDBCipherKeyManager->Get(aDatabase.Id(), cipherKeyId);

MOZ_RELEASE_ASSERT(key.isSome());
return MakeRefPtr<EncryptedFileBlobImpl>(aNativeFile, aId, *key);
}

auto impl = MakeRefPtr<FileBlobImpl>(aNativeFile);
impl->SetFileId(aId);

return impl;
}

Expand Down Expand Up @@ -6012,12 +6075,6 @@ using DatabaseActorHashtable =

StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable;

using PrivateBrowsingInfoHashtable = nsTHashMap<nsCStringHashKey, CipherKey>;
// XXX Maybe we can avoid a mutex here by moving all accesses to the background
// thread.
StaticAutoPtr<DataMutex<PrivateBrowsingInfoHashtable>>
gPrivateBrowsingInfoHashtable;

StaticRefPtr<ConnectionPool> gConnectionPool;

StaticRefPtr<FileHandleThreadPool> gFileHandleThreadPool;
Expand Down Expand Up @@ -6051,9 +6108,8 @@ void IncreaseBusyCount() {
MOZ_ASSERT(!gLiveDatabaseHashtable);
gLiveDatabaseHashtable = new DatabaseActorHashtable();

MOZ_ASSERT(!gPrivateBrowsingInfoHashtable);
gPrivateBrowsingInfoHashtable = new DataMutex<PrivateBrowsingInfoHashtable>(
"gPrivateBrowsingInfoHashtable");
MOZ_ASSERT(!gIndexedDBCipherKeyManager);
gIndexedDBCipherKeyManager = new IndexedDBCipherKeyManager();

MOZ_ASSERT(!gLoggingInfoHashtable);
gLoggingInfoHashtable = new DatabaseLoggingInfoHashtable();
Expand Down Expand Up @@ -6101,11 +6157,10 @@ void DecreaseBusyCount() {
MOZ_ASSERT(!gLiveDatabaseHashtable->Count());
gLiveDatabaseHashtable = nullptr;

MOZ_ASSERT(gPrivateBrowsingInfoHashtable);
MOZ_ASSERT(gIndexedDBCipherKeyManager);
// XXX After we add the private browsing session end listener, we can assert
// this.
// MOZ_ASSERT(!gPrivateBrowsingInfoHashtable->Count());
gPrivateBrowsingInfoHashtable = nullptr;
gIndexedDBCipherKeyManager = nullptr;

MOZ_ASSERT(gFactoryOps);
MOZ_ASSERT(gFactoryOps->IsEmpty());
Expand Down Expand Up @@ -12064,11 +12119,14 @@ DatabaseFileManager::MutexType DatabaseFileManager::sMutex;
DatabaseFileManager::DatabaseFileManager(
PersistenceType aPersistenceType,
const quota::OriginMetadata& aOriginMetadata,
const nsAString& aDatabaseName, bool aEnforcingQuota)
const nsAString& aDatabaseName, const nsCString& aDatabaseID,
bool aEnforcingQuota, bool aIsInPrivateBrowsingMode)
: mPersistenceType(aPersistenceType),
mOriginMetadata(aOriginMetadata),
mDatabaseName(aDatabaseName),
mEnforcingQuota(aEnforcingQuota) {}
mDatabaseID(aDatabaseID),
mEnforcingQuota(aEnforcingQuota),
mIsInPrivateBrowsingMode(aIsInPrivateBrowsingMode) {}

nsresult DatabaseFileManager::Init(nsIFile* aDirectory,
mozIStorageConnection& aConnection) {
Expand Down Expand Up @@ -15219,22 +15277,7 @@ nsresult FactoryOp::Open() {
MOZ_ASSERT(permission == PermissionValue::kPermissionAllowed);

if (mInPrivateBrowsing) {
const auto lockedPrivateBrowsingInfoHashtable =
gPrivateBrowsingInfoHashtable->Lock();

lockedPrivateBrowsingInfoHashtable->LookupOrInsertWith(mDatabaseId, [] {
IndexedDBCipherStrategy cipherStrategy;

// XXX Generate key using proper random data, such that we can ensure
// the use of unique IVs per key by discriminating by database's file
// id & offset.
auto keyOrErr = cipherStrategy.GenerateKey();

// XXX Propagate the error to the caller rather than asserting.
MOZ_RELEASE_ASSERT(keyOrErr.isOk());

return keyOrErr.unwrap();
});
gIndexedDBCipherKeyManager->Ensure(mDatabaseId);
}

mState = State::FinishOpen;
Expand Down Expand Up @@ -15842,19 +15885,11 @@ nsresult OpenDatabaseOp::DoDatabaseWork() {
CloneFileAndAppend(*dbDirectory, databaseFilenameBase +
kFileManagerDirectoryNameSuffix));

Maybe<const CipherKey> maybeKey;
if (mInPrivateBrowsing) {
CipherKey key;

{
const auto lockedPrivateBrowsingInfoHashtable =
gPrivateBrowsingInfoHashtable->Lock();
MOZ_ALWAYS_TRUE(
lockedPrivateBrowsingInfoHashtable->Get(mDatabaseId, &key));
}
Maybe<const CipherKey> maybeKey =
mInPrivateBrowsing ? gIndexedDBCipherKeyManager->Get(mDatabaseId)
: Nothing();

maybeKey.emplace(std::move(key));
}
MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome());

QM_TRY_UNWRAP(
NotNull<nsCOMPtr<mozIStorageConnection>> connection,
Expand Down Expand Up @@ -15897,7 +15932,8 @@ nsresult OpenDatabaseOp::DoDatabaseWork() {

if (!fileManager) {
fileManager = MakeSafeRefPtr<DatabaseFileManager>(
persistenceType, mOriginMetadata, databaseName, mEnforcingQuota);
persistenceType, mOriginMetadata, databaseName, mDatabaseId,
mEnforcingQuota, mInPrivateBrowsing);

QM_TRY(MOZ_TO_RESULT(fileManager->Init(fmDirectory, *connection)));

Expand Down Expand Up @@ -16557,19 +16593,11 @@ void OpenDatabaseOp::EnsureDatabaseActor() {
mMetadata = info->mMetadata.clonePtr();
}

Maybe<const CipherKey> maybeKey;
if (mInPrivateBrowsing) {
CipherKey key;

{
const auto lockedPrivateBrowsingInfoHashtable =
gPrivateBrowsingInfoHashtable->Lock();
MOZ_ALWAYS_TRUE(
lockedPrivateBrowsingInfoHashtable->Get(mDatabaseId, &key));
}
Maybe<const CipherKey> maybeKey =
mInPrivateBrowsing ? gIndexedDBCipherKeyManager->Get(mDatabaseId)
: Nothing();

maybeKey.emplace(std::move(key));
}
MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome());

// XXX Shouldn't Manager() return already_AddRefed when
// PBackgroundIDBFactoryParent is declared refcounted?
Expand Down Expand Up @@ -16872,16 +16900,11 @@ void DeleteDatabaseOp::LoadPreviousVersion(nsIFile& aDatabaseFile) {
return;
}

const auto maybeKey = [this]() -> Maybe<const CipherKey> {
CipherKey key;
const auto maybeKey = mInPrivateBrowsing
? gIndexedDBCipherKeyManager->Get(mDatabaseId)
: Nothing();

const auto lockedPrivateBrowsingInfoHashtable =
gPrivateBrowsingInfoHashtable->Lock();
if (!lockedPrivateBrowsingInfoHashtable->Get(mDatabaseId, &key)) {
return Nothing{};
}
return Some(std::move(key));
}();
MOZ_RELEASE_ASSERT(mInPrivateBrowsing == maybeKey.isSome());

// Pass -1 as the directoryLockId to disable quota checking, since we might
// temporarily exceed quota before deleting the database.
Expand Down Expand Up @@ -17152,6 +17175,11 @@ nsresult DeleteDatabaseOp::VersionChangeOp::RunOnIOThread() {
return rv;
}

if (mDeleteDatabaseOp->mInPrivateBrowsing) {
MOZ_ASSERT(
gIndexedDBCipherKeyManager->Remove(mDeleteDatabaseOp->mDatabaseId));
}

rv = mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
Expand Down Expand Up @@ -19527,11 +19555,22 @@ nsresult ObjectStoreAddOrPutRequestOp::DoDatabaseWork(
QM_TRY(OkIf(journalFile), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
IDB_REPORT_INTERNAL_ERR_LAMBDA);

nsCString fileKeyId;
fileKeyId.AppendInt(fileInfo.Id());

const auto maybeKey =
Transaction()
.GetDatabase()
.GetFileManager()
.IsInPrivateBrowsingMode()
? gIndexedDBCipherKeyManager->Get(
Transaction().GetDatabase().Id(), fileKeyId)
: Nothing();

QM_TRY(
MOZ_TO_RESULT(fileHelper->CreateFileFromStream(
*file, *journalFile, *inputStream,
storedFileInfo.ShouldCompress(),
Transaction().GetDatabase().MaybeKeyRef()))
storedFileInfo.ShouldCompress(), maybeKey))
.mapErr([](const nsresult rv) {
if (NS_ERROR_GET_MODULE(rv) !=
NS_ERROR_MODULE_DOM_INDEXEDDB) {
Expand Down
4 changes: 1 addition & 3 deletions dom/indexedDB/ActorsParentCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/dom/indexedDB/Key.h"
#include "mozilla/dom/quota/IPCStreamCipherStrategy.h"
#include "IndexedDBCipherKeyManager.h"
#include "nscore.h"
#include "nsISupports.h"
#include "nsStringFwd.h"
Expand All @@ -39,9 +40,6 @@ struct StructuredCloneReadInfoParent;

extern const nsLiteralString kJournalDirectoryName;

using IndexedDBCipherStrategy = quota::IPCStreamCipherStrategy;
using CipherKey = IndexedDBCipherStrategy::KeyType;

// At the moment, the encrypted stream block size is assumed to be unchangeable
// between encrypting and decrypting blobs. This assumptions holds as long as we
// only encrypt in private browsing mode, but when we support encryption for
Expand Down
9 changes: 7 additions & 2 deletions dom/indexedDB/DatabaseFileManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ class DatabaseFileManager final
const PersistenceType mPersistenceType;
const quota::OriginMetadata mOriginMetadata;
const nsString mDatabaseName;
const nsCString mDatabaseID;

LazyInitializedOnce<const nsString> mDirectoryPath;
LazyInitializedOnce<const nsString> mJournalDirectoryPath;

const bool mEnforcingQuota;
const bool mIsInPrivateBrowsingMode;

// Lock protecting DatabaseFileManager.mFileInfos.
// It's s also used to atomically update DatabaseFileInfo.mRefCnt and
Expand All @@ -59,7 +61,9 @@ class DatabaseFileManager final

DatabaseFileManager(PersistenceType aPersistenceType,
const quota::OriginMetadata& aOriginMetadata,
const nsAString& aDatabaseName, bool aEnforcingQuota);
const nsAString& aDatabaseName,
const nsCString& aDatabaseID, bool aEnforcingQuota,
bool aIsInPrivateBrowsingMode);

PersistenceType Type() const { return mPersistenceType; }

Expand All @@ -70,7 +74,8 @@ class DatabaseFileManager final
const nsACString& Origin() const { return mOriginMetadata.mOrigin; }

const nsAString& DatabaseName() const { return mDatabaseName; }

const nsCString& DatabaseID() const { return mDatabaseID; }
auto IsInPrivateBrowsingMode() const { return mIsInPrivateBrowsingMode; }
bool EnforcingQuota() const { return mEnforcingQuota; }

nsresult Init(nsIFile* aDirectory, mozIStorageConnection& aConnection);
Expand Down
Loading

0 comments on commit 874e0b7

Please sign in to comment.