Bug 1858696: Created IDB and CacheAPI marionette tests to verify raw magic strings in on-disk storages.r=dom-storage-reviewers,webdriver-reviewers,whimboo,janv

Differential Revision: https://phabricator.services.mozilla.com/D192328
This commit is contained in:
hsingh 2024-02-02 15:14:24 +00:00
parent 1eb7ddbd5d
commit 2cf947cbda
5 changed files with 134 additions and 68 deletions

View file

@ -26,8 +26,8 @@ class CacheAPIEncryptionPBM(QuotaTestCase):
def setUp(self): def setUp(self):
super(CacheAPIEncryptionPBM, self).setUp() super(CacheAPIEncryptionPBM, self).setUp()
self.testHTML = "dom/cache/basicCacheAPI_PBM.html"
self.cacheName = "CachePBMTest" self.cacheName = "CachePBMTest"
self.profilePath = self.marionette.instance.profile.profile self.profilePath = self.marionette.instance.profile.profile
self.cacheAPIStoragePath = None self.cacheAPIStoragePath = None
@ -45,27 +45,33 @@ class CacheAPIEncryptionPBM(QuotaTestCase):
self.dbCheckpointThresholdBytes = 512 * 1024 self.dbCheckpointThresholdBytes = 512 * 1024
# Navigate by opening a new private window
pbmWindowHandle = self.marionette.open(type="window", private=True)["handle"]
self.marionette.switch_to_window(pbmWindowHandle)
self.marionette.navigate(
self.marionette.absolute_url("dom/cache/basicCacheAPI_PBM.html")
)
self.origin = self.marionette.absolute_url("")[:-1] + "^privateBrowsingId=1"
def tearDown(self): def tearDown(self):
super(CacheAPIEncryptionPBM, self).setUp() super(CacheAPIEncryptionPBM, self).setUp()
self.marionette.set_pref(CACHEAPI_PBM_PREF, self.defaultCacheAPIPBMPref) self.marionette.set_pref(CACHEAPI_PBM_PREF, self.defaultCacheAPIPBMPref)
self.marionette.set_pref(QM_TESTING_PREF, self.defaultQMPrefValue) self.marionette.set_pref(QM_TESTING_PREF, self.defaultQMPrefValue)
self.marionette.execute_script("window.wrappedJSObject.releaseCache()") def test_request_response_ondisk(self):
with self.using_new_window(self.testHTML, private=False) as (
self.origin,
self.persistenceType,
):
self.runAndValidate(
lambda exists: self.assertTrue(
exists, "Failed to find expected data on disk"
)
)
# closes the new private window we opened in the setUp and referred by 'pbmWindowHandle' def test_encrypted_request_response_ondisk(self):
self.marionette.close() with self.using_new_window(self.testHTML, private=True) as (
self.origin,
self.persistenceType,
):
self.runAndValidate(
lambda exists: self.assertFalse(exists, "Data on disk is not encrypted")
)
def test_ensure_encrypted_request_response(self): def runAndValidate(self, validator):
self.marionette.execute_async_script( self.marionette.execute_async_script(
""" """
const [name, requestStr, responseStr, resolve] = arguments; const [name, requestStr, responseStr, resolve] = arguments;
@ -73,24 +79,32 @@ class CacheAPIEncryptionPBM(QuotaTestCase):
const request = new Request(requestStr); const request = new Request(requestStr);
const response = new Response(responseStr); const response = new Response(responseStr);
window.wrappedJSObject.addDataIntoCache(name, request, response) window.wrappedJSObject.addDataIntoCache(name, request, response)
.then(()=> { resolve(); }); .then(resolve);
""", """,
script_args=(self.cacheName, self.cacheRequestStr, self.cacheResponseStr), script_args=(
self.cacheName,
self.cacheRequestStr,
self.cacheResponseStr,
),
) )
self.ensureInvariantHolds( self.ensureInvariantHolds(
lambda _: os.path.exists(self.getCacheAPIStoragePath()) lambda _: os.path.exists(self.getCacheAPIStoragePath())
) )
self.ensureSqliteIsEncrypted() self.validateSqlite(validator)
self.validateBodyFile(validator)
def validateBodyFile(self, validator):
# Ensure response bodies have been flushed to the disk # Ensure response bodies have been flushed to the disk
self.ensureInvariantHolds( self.ensureInvariantHolds(
lambda _: self.findDirObj(self.cacheAPIStoragePath, "morgue", False) lambda _: self.findDirObj(self.getCacheAPIStoragePath(), "morgue", False)
is not None is not None
) )
cacheResponseDir = self.findDirObj(self.cacheAPIStoragePath, "morgue", False) cacheResponseDir = self.findDirObj(
self.getCacheAPIStoragePath(), "morgue", False
)
self.ensureInvariantHolds(lambda _: any(os.listdir(cacheResponseDir))) self.ensureInvariantHolds(lambda _: any(os.listdir(cacheResponseDir)))
@ -114,26 +128,28 @@ class CacheAPIEncryptionPBM(QuotaTestCase):
with open(cacheResponseBodyPath, "rb") as f_binary: with open(cacheResponseBodyPath, "rb") as f_binary:
foundRawValue = re.search(b"sNaPpY", f_binary.read()) is not None foundRawValue = re.search(b"sNaPpY", f_binary.read()) is not None
self.assertFalse(foundRawValue, "Response body did not get encrypted") validator(foundRawValue)
def ensureSqliteIsEncrypted(self): def validateSqlite(self, validator):
self.ensureInvariantHolds( self.ensureInvariantHolds(
lambda _: self.findDirObj( lambda _: self.findDirObj(
self.cacheAPIStoragePath, self.cacheDBJournalFileName, True self.getCacheAPIStoragePath(), self.cacheDBJournalFileName, True
) )
is not None is not None
) )
dbJournalFile = self.findDirObj( dbJournalFile = self.findDirObj(
self.cacheAPIStoragePath, self.cacheDBJournalFileName, True self.getCacheAPIStoragePath(), self.cacheDBJournalFileName, True
) )
self.ensureInvariantHolds( self.ensureInvariantHolds(
lambda _: self.findDirObj( lambda _: self.findDirObj(
self.cacheAPIStoragePath, self.cacheDBFileName, True self.getCacheAPIStoragePath(), self.cacheDBFileName, True
) )
is not None is not None
) )
dbFile = self.findDirObj(self.cacheAPIStoragePath, self.cacheDBFileName, True) dbFile = self.findDirObj(
self.getCacheAPIStoragePath(), self.cacheDBFileName, True
)
# Confirm journal file size is less than 512KB which ensures that checkpoint # Confirm journal file size is less than 512KB which ensures that checkpoint
# has not happend yet (dom/cache/DBSchema.cpp::InitializeConnection, kWalAutoCheckpointPages) # has not happend yet (dom/cache/DBSchema.cpp::InitializeConnection, kWalAutoCheckpointPages)
@ -144,26 +160,27 @@ class CacheAPIEncryptionPBM(QuotaTestCase):
# Before checkpointing, journal file size should be greater than main sqlite db file. # Before checkpointing, journal file size should be greater than main sqlite db file.
self.assertTrue(os.path.getsize(dbJournalFile) > os.path.getsize(dbFile)) self.assertTrue(os.path.getsize(dbJournalFile) > os.path.getsize(dbFile))
self.assertFalse( validator(
self.cacheRequestStr.encode("ascii") in open(dbJournalFile, "rb").read(), self.cacheRequestStr.encode("ascii") in open(dbJournalFile, "rb").read()
"Sqlite journal file did not get encrypted",
) )
self.assertTrue(self.resetStoragesForPrincipal(self.origin, "private", "cache")) self.assertTrue(
self.resetStoragesForPrincipal(self.origin, self.persistenceType, "cache")
)
self.assertFalse(os.path.getsize(dbJournalFile) > os.path.getsize(dbFile)) self.assertFalse(os.path.getsize(dbJournalFile) > os.path.getsize(dbFile))
self.assertFalse( validator(self.cacheRequestStr.encode("ascii") in open(dbFile, "rb").read())
self.cacheRequestStr.encode("ascii") in open(dbFile, "rb").read(),
"Sqlite main db file did not get encrypted",
)
def getCacheAPIStoragePath(self): def getCacheAPIStoragePath(self):
if self.cacheAPIStoragePath is not None: if self.cacheAPIStoragePath is not None:
return self.cacheAPIStoragePath return self.cacheAPIStoragePath
assert self.origin is not None
assert self.persistenceType is not None
self.cacheAPIStoragePath = self.getStoragePath( self.cacheAPIStoragePath = self.getStoragePath(
self.profilePath, self.origin, "private", "cache" self.profilePath, self.origin, self.persistenceType, "cache"
) )
print("cacheAPI origin directory = " + self.cacheAPIStoragePath) print("cacheAPI origin directory = " + self.cacheAPIStoragePath)

View file

@ -26,6 +26,7 @@ class IDBEncryptionPBM(QuotaTestCase):
def setUp(self): def setUp(self):
super(IDBEncryptionPBM, self).setUp() super(IDBEncryptionPBM, self).setUp()
self.testHTML = "dom/indexedDB/basicIDB_PBM.html"
self.IDBName = "IDBTest" self.IDBName = "IDBTest"
self.IDBStoreName = "IDBTestStore" self.IDBStoreName = "IDBTestStore"
self.IDBVersion = 1 self.IDBVersion = 1
@ -40,41 +41,61 @@ class IDBEncryptionPBM(QuotaTestCase):
self.defaultQMPrefValue = self.marionette.get_pref(QM_TESTING_PREF) self.defaultQMPrefValue = self.marionette.get_pref(QM_TESTING_PREF)
self.marionette.set_pref(QM_TESTING_PREF, True) self.marionette.set_pref(QM_TESTING_PREF, True)
# Navigate by opening a new private window
pbmWindowHandle = self.marionette.open(type="window", private=True)["handle"]
self.marionette.switch_to_window(pbmWindowHandle)
self.marionette.navigate(
self.marionette.absolute_url("dom/indexedDB/basicIDB_PBM.html")
)
self.origin = self.marionette.absolute_url("")[:-1] + "^privateBrowsingId=1"
def tearDown(self): def tearDown(self):
super(IDBEncryptionPBM, self).setUp() super(IDBEncryptionPBM, self).tearDown()
self.marionette.set_pref(INDEXED_DB_PBM_PREF, self.defaultIDBPrefValue) self.marionette.set_pref(INDEXED_DB_PBM_PREF, self.defaultIDBPrefValue)
self.marionette.set_pref(QM_TESTING_PREF, self.defaultQMPrefValue) self.marionette.set_pref(QM_TESTING_PREF, self.defaultQMPrefValue)
# closes the new private window we opened in the setUp and referred by 'pbmWindowHandle' def test_raw_IDB_data_ondisk(self):
self.marionette.close() with self.using_new_window(self.testHTML, private=False) as (
self.origin,
self.persistenceType,
):
self.runAndValidate(
lambda exists: self.assertTrue(
exists, "Failed to find expected data on disk"
)
)
def test_ensure_encrypted_blob(self): def test_ensure_encrypted_IDB_data_ondisk(self):
self.marionette.execute_script( with self.using_new_window(self.testHTML, private=True) as (
self.origin,
self.persistenceType,
):
self.runAndValidate(
lambda exists: self.assertFalse(exists, "Data on disk is not encrypted")
)
def runAndValidate(self, validator):
self.marionette.execute_async_script(
""" """
const [idb, store, key, value] = arguments; const [idb, store, key, value, resolve] = arguments;
window.wrappedJSObject.addDataIntoIDB(idb, store, key, value).then(resolve);
""",
script_args=(self.IDBName, self.IDBStoreName, "textKey", self.IDBValue),
)
self.validateSqlite(validator)
self.marionette.execute_async_script(
"""
const [idb, store, key, value, resolve] = arguments;
const blobValue = new Blob([value], {type:'text/plain'}); const blobValue = new Blob([value], {type:'text/plain'});
window.wrappedJSObject.addDataIntoIDB(idb, store, key, blobValue); window.wrappedJSObject.addDataIntoIDB(idb, store, key, blobValue).then(resolve);
""", """,
script_args=(self.IDBName, self.IDBStoreName, "blobKey", self.IDBValue), script_args=(self.IDBName, self.IDBStoreName, "blobKey", self.IDBValue),
) )
self.validateBlob(validator)
def validateBlob(self, validator):
self.ensureInvariantHolds(lambda _: self.sqliteWALReleased()) self.ensureInvariantHolds(lambda _: self.sqliteWALReleased())
self.ensureInvariantHolds( self.ensureInvariantHolds(
lambda _: self.findDirObj(self.idbStoragePath, ".files", False) is not None lambda _: self.findDirObj(self.getIDBStoragePath(), ".files", False)
is not None
) )
idbBlobDir = self.findDirObj(self.idbStoragePath, ".files", False) idbBlobDir = self.findDirObj(self.getIDBStoragePath(), ".files", False)
# seems like there's a timing issue here. There are sometimes no blob file # seems like there's a timing issue here. There are sometimes no blob file
# even after WAL is released. Allowing some buffer time and ensuring blob file # even after WAL is released. Allowing some buffer time and ensuring blob file
@ -88,23 +109,16 @@ class IDBEncryptionPBM(QuotaTestCase):
re.search(self.IDBValue.encode("ascii"), f_binary.read()) is not None re.search(self.IDBValue.encode("ascii"), f_binary.read()) is not None
) )
self.assertFalse(foundRawValue, "Blob file did not get encrypted") validator(foundRawValue)
def test_ensure_encrpted_sqlite_data(self):
self.marionette.execute_script(
"""
const [idb, store, key, value] = arguments;
window.wrappedJSObject.addDataIntoIDB(idb, store, key, value);
""",
script_args=(self.IDBName, self.IDBStoreName, "textKey", self.IDBValue),
)
def validateSqlite(self, validator):
self.ensureInvariantHolds(lambda _: self.sqliteWALReleased()) self.ensureInvariantHolds(lambda _: self.sqliteWALReleased())
self.ensureInvariantHolds( self.ensureInvariantHolds(
lambda _: self.findDirObj(self.idbStoragePath, ".sqlite", True) is not None lambda _: self.findDirObj(self.getIDBStoragePath(), ".sqlite", True)
is not None
) )
sqliteDBFile = self.findDirObj(self.idbStoragePath, ".sqlite", True) sqliteDBFile = self.findDirObj(self.getIDBStoragePath(), ".sqlite", True)
foundRawValue = False foundRawValue = False
with open(sqliteDBFile, "rb") as f_binary: with open(sqliteDBFile, "rb") as f_binary:
@ -112,14 +126,17 @@ class IDBEncryptionPBM(QuotaTestCase):
re.search(self.IDBValue.encode("ascii"), f_binary.read()) is not None re.search(self.IDBValue.encode("ascii"), f_binary.read()) is not None
) )
self.assertFalse(foundRawValue, "sqlite data did not get encrypted") validator(foundRawValue)
def getIDBStoragePath(self): def getIDBStoragePath(self):
if self.idbStoragePath is not None: if self.idbStoragePath is not None:
return self.idbStoragePath return self.idbStoragePath
assert self.origin is not None
assert self.persistenceType is not None
self.idbStoragePath = self.getStoragePath( self.idbStoragePath = self.getStoragePath(
self.profilePath, self.origin, "private", "idb" self.profilePath, self.origin, self.persistenceType, "idb"
) )
print("idb origin directory = " + self.idbStoragePath) print("idb origin directory = " + self.idbStoragePath)

View file

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os import os
from contextlib import contextmanager
from marionette_driver import Wait from marionette_driver import Wait
from marionette_harness import MarionetteTestCase from marionette_harness import MarionetteTestCase
@ -88,3 +89,25 @@ class QuotaTestCase(MarionetteTestCase):
with self.marionette.using_context(self.marionette.CONTEXT_CHROME): with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
return self.marionette.execute_async_script(script) return self.marionette.execute_async_script(script)
@contextmanager
def using_new_window(self, path, private=False):
"""
This helper method is created to ensure that a temporary
window required inside the test scope is lifetime'd properly
"""
oldWindow = self.marionette.current_window_handle
try:
newWindow = self.marionette.open(type="window", private=private)
self.marionette.switch_to_window(newWindow["handle"])
self.marionette.navigate(self.marionette.absolute_url(path))
origin = self.marionette.absolute_url("")[:-1]
if private:
origin += "^privateBrowsingId=1"
yield (origin, "private" if private else "default")
finally:
self.marionette.close()
self.marionette.switch_to_window(oldWindow)

View file

@ -14,7 +14,7 @@
async function addDataIntoCache(name, request, response) { async function addDataIntoCache(name, request, response) {
let cache = await ensureCache(name); let cache = await ensureCache(name);
await cache.put(request, response); return cache.put(request, response);
}; };
</script> </script>
</head> </head>

View file

@ -26,7 +26,8 @@
await (new Promise((resolve, reject) => { await (new Promise((resolve, reject) => {
var transaction = db.transaction([store], "readwrite"); var transaction = db.transaction([store], "readwrite");
var put = transaction.objectStore(store).put(value, key); var put = transaction.objectStore(store).put(value, key);
put.onsuccess = resolve(); put.onerror = reject;
put.onsuccess = resolve;
})); }));
closeIDB(db) closeIDB(db)
@ -35,6 +36,14 @@
function closeIDB(db) { function closeIDB(db) {
db.close(); db.close();
} }
function deleteIDB(db) {
return new Promise((resolve, reject) => {
let deleteReq = indexedDB.deleteDatabase(db);
deleteReq.onerror = reject;
deleteReq.onsuccess = resolve;
});
}
</script> </script>
</head> </head>
</html> </html>