Skip to content

Instantly share code, notes, and snippets.

@asutherland
Last active November 8, 2023 04:43
Show Gist options
  • Save asutherland/e192788e2a346a25e235841ab4325afd to your computer and use it in GitHub Desktop.
Save asutherland/e192788e2a346a25e235841ab4325afd to your computer and use it in GitHub Desktop.
Private Browsing Validation Steps

asuth's Cache API Private Browsing Validation Steps

Note that all steps are being perform on Ubuntu linux with the following packages installed:

  • binutils-x86-64-linux-gnu for the strings command line tool.
    • Note that llvm also provides a strings tool, for example the llvm-14 package provides llvm-strings-14 which is equivalent for our purposes.
  • coreutils for the od command line tool.
  • ent for the ent command line tool whose homepage is https://www.fourmilab.ch/random/ (but whose debian package has diverged).
  • uuid for the uuid command line tool.

Profile generation / control

I am using the --profile PROFILE-PATH command line argument combined with -no-remote for testing purposes.

Because I am testing in a build where dom.cache.privateBrowsing.enabled defaults to false, I first manually configure the profile to set the preference to true, then restart.

General Operation and Cache database encryption

Steps:

  1. Open a PB window.
  2. Open the URL https://firefox-storage-test.glitch.me/ and ensure that it reports that:
    • LocalStorage, QuotaManager, IndexedDB, and Cache API are "Good: Totally Working."
  3. Validate that PROFILE/storage/private is created and change into that directory. Future steps will refer to this directory as PBSTORAGE.
  4. Validate that there is a directory that starts with uuid+++ and is followed by a UUID. For example, uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03. All characters in the UUID will match the regex character range [-0-9a-z].
    • It's important to ensure that the UUID is a version 4 random UUID that is variant 1. We can confirm this by matching against the pattern xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx and ensuring that M is 4 and N has a bit pattern of 10xx which means it matches the regex [89ab]. We can also use command line tools to parse like uuid -d THEUUID will say version: 4 (random data based). Other tools like uuidparse also exist.
  5. Run /usr/bin/find . -printf "%p %sB\n" in PBSTORAGE order to dump the entirety of the tree, including hidden dot files. For my run, I got:
. 4096B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03 4096B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache 4096B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/morgue 4096B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/morgue/172 4096B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/morgue/172/{8511b9e6-c48f-4a23-a561-4639c1fb8bac}.final 12288B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/morgue/36 4096B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/morgue/36/{28b9798c-4714-4150-8b75-8edbfdbfa424}.final 4096B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/caches.sqlite 131072B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/caches.sqlite-wal 254728B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/context_open.marker 0B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/.padding 8B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/idb 4096B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/idb/742c9a20-94f1-42ec-a196-ba9dd8c03f96.files 4096B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/idb/742c9a20-94f1-42ec-a196-ba9dd8c03f96.sqlite-wal 0B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/idb/742c9a20-94f1-42ec-a196-ba9dd8c03f96.sqlite 90112B
./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/.metadata-v2 73B
  1. We expect to find in the sole directory:
    • A .metadata-v2 file which may be removed in the future.
    • The idb IndexedDB database directory.
      • A database of the form UUID.sqlite as well as a paired UUID.files directory using the same UUID which holds Blobs stored in the database or very large structured clone values.
      • We now always expect to find .sqlite-journal or .sqlite-wal or .sqlite-shm suffixed files because we have changed Firefox to retain the files for performance reasons. This means they will have a non-zero size when the database is actively open, but once it has been closed, the files should be truncated to zero length. For https://firefox-storage-test.glitch.me/ we expect the database to be closed in a timely fashion, so you may want to re-run the find command to see if those files have gone away.
    • The caches Cache API directory.
      • The morgue directory, a poorly named directory that contains the Response bodies using an additional tier of sub-directories to shard the UUID-based file names. We expect a total of 2 Response body files with filenames ending in .final. One should be 4 KiB (4096 B) and one should be 12 KiB (12288 B).
      • The caches.sqlite and its Write-Ahead-Log counterpart, caches.sqlite-wal. When the cache is open, we expect the .sqlite-wal file to have a non-zero size (254,728 B i this case), but to shrink to 0 B when the cache is closed. In this case the .sqlite file was 128 KiB (131,072 B) both when open and after checkpointing.
      • context_open.marker a marker file that's present when the cache is open, with a 0 length.
      • .padding is always present and always 8 B in size. It conveys the amount of padding for no-cors responses without us having to open the database.
  2. Verify that the .metadata-v2 file contains the UUID and not the original origin name. Note that I will be using relative paths from the root of PBSTORAGE in my example, but it's fine to change into the directory you are investigating and just use the file names.
    • Run the strings command to dump the runs of printable characters at least 4 characters long on the metadata file. Verify that the string corresponds to the UUID. For example, my run looks like:
$ strings ./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/.metadata-v2 
+uuid://4ae1fe8a-9f6e-428d-9eee-4a57707a0a03
  • Run the od -t x1z command to dump the entire contents of the file and verify there are no other notable pieces of information that look concerning. The data should largely look like the following where the first 8 bytes are a timestamp (which will change), followed by a bunch of zeroes. The sanitized origin that starts with uuid:// will also be prefixed by a length byte which will render as + because all the UUIDs have the same length. My run looked like:
$ /usr/bin/od -t x1z ./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/.metadata-v2 
0000000 00 06 09 9c 14 24 ec 26 00 00 00 00 00 00 00 00  >.....$.&........<
0000020 00 00 00 00 00 00 00 00 00 00 00 00 2b 75 75 69  >............+uui<
0000040 64 3a 2f 2f 34 61 65 31 66 65 38 61 2d 39 66 36  >d://4ae1fe8a-9f6<
0000060 65 2d 34 32 38 64 2d 39 65 65 65 2d 34 61 35 37  >e-428d-9eee-4a57<
0000100 37 30 37 61 30 61 30 33 01                       >707a0a03.<
0000111
  1. Verify that each SQLite database appears encrypted by using the ent command on the database to verify that there is a high about of entropy per bit. Note that a hard-coded SQLite header is used in the clear at the very beginning of the file, so using the file command will correctly identify the that the file is a SQLite database. Because the ciphertext should look like a high-quality stream of (pseudo)random data, we expect to see an entropy close to 8 bits per byte. This is what my ent invocations look like when using find to run ent on all the relevant non-zero-length files at once using /usr/bin/find . -name \*.sqlite\* -size +0 -printf "%p %sB\n" -exec ent '{}' \; -exec echo -e "\n---\n" \;
Entropy = 7.511740 bits per byte.

Optimum compression would reduce the size
of this 131072 byte file by 6 percent.

Chi square distribution for 131072 samples is 534588.14, and randomly
would exceed this value less than 0.01 percent of the times.

Arithmetic mean value of data bytes is 111.7017 (127.5 = random).
Monte Carlo value for Pi is 3.232593271 (error 2.90 percent).
Serial correlation coefficient is 0.271889 (totally uncorrelated = 0.0).

---

./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/cache/caches.sqlite-wal 254728B
Entropy = 7.998382 bits per byte.

Optimum compression would reduce the size
of this 254728 byte file by 0 percent.

Chi square distribution for 254728 samples is 614.91, and randomly
would exceed this value less than 0.01 percent of the times.

Arithmetic mean value of data bytes is 127.3905 (127.5 = random).
Monte Carlo value for Pi is 3.133273661 (error 0.26 percent).
Serial correlation coefficient is 0.006572 (totally uncorrelated = 0.0).

---

./uuid+++4ae1fe8a-9f6e-428d-9eee-4a57707a0a03/idb/742c9a20-94f1-42ec-a196-ba9dd8c03f96.sqlite 90112B
Entropy = 7.996701 bits per byte.

Optimum compression would reduce the size
of this 90112 byte file by 0 percent.

Chi square distribution for 90112 samples is 418.14, and randomly
would exceed this value less than 0.01 percent of the times.

Arithmetic mean value of data bytes is 127.4524 (127.5 = random).
Monte Carlo value for Pi is 3.170861633 (error 0.93 percent).
Serial correlation coefficient is 0.001525 (totally uncorrelated = 0.0).

---

  1. A potential sanity check at this time while we currently do not encrypt IDB databases for non-private browsing mode is to also run ent against the same database for non-private-browisng. This is an example invocation I ran from the directory PBPROFILE/storage/default/https+++firefox-storage-test.glitch.me noting that we also have LocalStorage present in this case, whereas for private browsing it is only retained in memory and not currently written to disk.
$ /usr/bin/find . -name \*.sqlite\* -size +0 -printf "%p %sB\n" -exec ent '{}' \; -exec echo -e "\n---\n" \;
./cache/caches.sqlite 98304B
Entropy = 1.906636 bits per byte.

Optimum compression would reduce the size
of this 98304 byte file by 76 percent.

Chi square distribution for 98304 samples is 16278142.56, and randomly
would exceed this value less than 0.01 percent of the times.

Arithmetic mean value of data bytes is 16.1374 (127.5 = random).
Monte Carlo value for Pi is 3.998291016 (error 27.27 percent).
Serial correlation coefficient is 0.893347 (totally uncorrelated = 0.0).

---

./idb/1984578081ptenrestis.sqlite 49152B
Entropy = 0.757212 bits per byte.

Optimum compression would reduce the size
of this 49152 byte file by 90 percent.

Chi square distribution for 49152 samples is 10818176.36, and randomly
would exceed this value less than 0.01 percent of the times.

Arithmetic mean value of data bytes is 5.8571 (127.5 = random).
Monte Carlo value for Pi is 3.999511719 (error 27.31 percent).
Serial correlation coefficient is 0.910430 (totally uncorrelated = 0.0).

---

./ls/data.sqlite 6144B
Entropy = 1.405298 bits per byte.

Optimum compression would reduce the size
of this 6144 byte file by 82 percent.

Chi square distribution for 6144 samples is 1157584.75, and randomly
would exceed this value less than 0.01 percent of the times.

Arithmetic mean value of data bytes is 10.9212 (127.5 = random).
Monte Carlo value for Pi is 4.000000000 (error 27.32 percent).
Serial correlation coefficient is 0.825137 (totally uncorrelated = 0.0).

---

  1. An additional step we can take is to run the strings command against the encrypted database as just an additional level of sanity checking. As noted above, we do expect to find the string SQLite format 3 in the clear because it is part of the hardcoded header that is stored in the clear. In order to reduce false positives it is possible to pass an -n argument like strings -n8 to only show sequences of 8-or-more printable characters which will produce fewer lines of output. Because the random output should be uniformly distributed over all values in the range 0-255, we do expect to see multiple runs of (gibberish) printable characters. Our revised "find" invocation is: /usr/bin/find . -name \*.sqlite\* -size +0 -printf "%p %sB\n" -exec strings -n8 '{}' \; -exec echo -e "\n---\n" \; and produces the expected result of "SQLite format 3" followed by a random set of random printable characters.

At this point we have verified that:

  • The file/directory names do not include the (real) origin name in the clear.
  • That the metadata file likewise does not contain the (real) origin name in the clear.
  • That the file is encrypted although it includes the expected SQLite header.

Cache API Response Encryption

Our Cache API implementation stores the Response bodies as individual files under the "morgue" directory.

Ent invocation:

/usr/bin/find . -name \*.final -size +0 -printf "%p %sB\n" -exec ent '{}' \; -exec echo -e "\n---\n" \;

asuth's IDB Private Browsing Validation Steps

Note that some changes have happened in terms of how we handle .sqlite-journal and .sqlite-wal files. We now truncate them to zero length but do not delete them, so they will always be present. This document has not been updated to compensate for this, but the Cache API document has.

Note that all steps are being perform on Ubuntu linux with the following packages installed:

  • binutils-x86-64-linux-gnu for the strings command line tool.
    • Note that llvm also provides a strings tool, for example the llvm-14 package provides llvm-strings-14 which is equivalent for our purposes.
  • coreutils for the od command line tool.
  • ent for the ent command line tool whose homepage is https://www.fourmilab.ch/random/ (but whose debian package has diverged).
  • uuid for the uuid command line tool.

General Operation and IDB database encryption

Steps:

  1. Open a PB window.
  2. Open the URL https://firefox-storage-test.glitch.me/ and ensure that it reports that:
    • LocalStorage, QuotaManager, and IndexedDB are "Good: Totally Working."
    • Currently Cache API will say "Bad: Totally Broken", but this will become "Totally Working" in the future.
  3. Validate that PROFILE/storage/private is created and change into that directory. Future steps will refer to this directory as PBSTORAGE.
  4. Validate that there is a directory that starts with uuid+++ and is followed by a UUID. For example, uuid+++d0931324-3b24-4e37-a618-18030cd28373. All characters in the UUID will match the regex character range [-0-9a-z].
    • It's important to ensure that the UUID is a version 4 random UUID that is variant 1. We can confirm this by matching against the pattern xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx and ensuring that M is 4 and N has a bit pattern of 10xx which means it matches the regex [89ab]. We can also use command line tools to parse like uuid -d THEUUID will say version: 4 (random data based). Other tools like uuidparse also exist.
  5. Run /usr/bin/find . in PBSTORAGE order to dump the entirety of the tree, including hidden dot files. For my run, I got:
.
./uuid+++d0931324-3b24-4e37-a618-18030cd28373
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/.metadata-v2
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/ba95b3a6-1619-4b54-8169-1fa916483fc1.sqlite
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/ba95b3a6-1619-4b54-8169-1fa916483fc1.files
  1. We expect to find in the sole directory:
    • A .metadata-v2 file which may be removed in the future.
    • A database of the form UUID.sqlite as well as a paired UUID.files directory using the same UUID which holds Blobs stored in the database or very large structured clone values.
    • If there are any .sqlite-journal or .sqlite-wal or .sqlite-shm suffixed files, this is expected when the underlying SQLite database is actively open. For https://firefox-storage-test.glitch.me/ we expect the database to be closed in a timely fashion, so you may want to re-run the find command to see if those files have gone away.
  2. Verify that the .metadata-v2 file contains the UUID and not the original origin name. Note that I will be using relative paths from the root of PBSTORAGE in my example, but it's fine to change into the directory you are investigating and just use the file names.
    • Run the strings command to dump the runs of printable characters at least 4 characters long on the metadata file. Verify that the string corresponds to the UUID. For example, my run looks like:
$ strings ./uuid+++d0931324-3b24-4e37-a618-18030cd28373/.metadata-v2
+uuid://d0931324-3b24-4e37-a618-18030cd28373
  • Run the od -t x1z command to dump the entire contents of the file and verify there are no other notable pieces of information that look concerning. The data should largely look like the following where the first 8 bytes are a timestamp (which will change), followed by a bunch of zeroes. The sanitized origin that starts with uuid:// will also be prefixed by a length byte which will render as + because all the UUIDs have the same length. My run looked like:
$ /usr/bin/od -t x1z ./uuid+++d0931324-3b24-4e37-a618-18030cd28373/.metadata-v2
0000000 00 05 fb 32 e9 23 3d e9 00 00 00 00 00 00 00 00  >...2.#=.........<
0000020 00 00 00 00 00 00 00 00 00 00 00 00 2b 75 75 69  >............+uui<
0000040 64 3a 2f 2f 64 30 39 33 31 33 32 34 2d 33 62 32  >d://d0931324-3b2<
0000060 34 2d 34 65 33 37 2d 61 36 31 38 2d 31 38 30 33  >4-4e37-a618-1803<
0000100 30 63 64 32 38 33 37 33 01                       >0cd28373.<
0000111
  1. Verify that the SQLite database appears encrypted by using the ent command on the database to verify that there is a high about of entropy per bit. Note that a hard-coded SQLite header is used in the clear at the very beginning of the file, so using the file command will correctly identify the that the file is a SQLite database. Because the ciphertext should look like a high-quality stream of (pseudo)random data, we expect to see an entropy close to 8 bits per byte. This is what my ent invocation looks like, and indeed reports a very high entropy per byte:
$ ent uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/ba95b3a6-1619-4b54-8169-1fa916483fc1.sqlite
Entropy = 7.996894 bits per byte.

Optimum compression would reduce the size
of this 81920 byte file by 0 percent.

Chi square distribution for 81920 samples is 354.79, and randomly
would exceed this value less than 0.01 percent of the times.

Arithmetic mean value of data bytes is 126.4566 (127.5 = random).
Monte Carlo value for Pi is 3.165604629 (error 0.76 percent).
Serial correlation coefficient is 0.006158 (totally uncorrelated = 0.0).
  1. A potential sanity check at this time while we currently do not encrypt IDB databases for non-private browsing mode is to also run ent against the same database for non-private-browisng. This is an example invocation I ran:
$ ent storage/default/https+++firefox-storage-test.glitch.me/idb/1984578081ptenrestis.sqlite
Entropy = 0.757457 bits per byte.

Optimum compression would reduce the size
of this 49152 byte file by 90 percent.

Chi square distribution for 49152 samples is 10818175.61, and randomly
would exceed this value less than 0.01 percent of the times.

Arithmetic mean value of data bytes is 5.8568 (127.5 = random).
Monte Carlo value for Pi is 3.999511719 (error 27.31 percent).
Serial correlation coefficient is 0.908720 (totally uncorrelated = 0.0).
  1. An additional step we can take is to run the strings command against the encrypted database as just an additional level of sanity checking. As noted above, we do expect to find the string SQLite format 3 in the clear because it is part of the hardcoded header that is stored in the clear. This can be run like strings uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/ba95b3a6-1619-4b54-8169-1fa916483fc1.sqlite and should consist of that SQLite header string followed by various runs of gibberish. It's also possible to pass an -n argument to strings like strings -n8 to only show sequences of 8-or-more printable characters which will produce fewer lines of output. Because the random output should be uniformly distributed over all values in the range 0-255, we do expect to see multiple runs of (gibberish) printable characters.
    • When run against the unecnrypted database, we expect to mainly see the DB schema plus the object store name ("persistent") and origin ("https://firefox-storage-test.glitch.me"). This would look like the following run:
$ strings -n8 storage/default/https+++firefox-storage-test.glitch.me/idb/1984578081ptenrestis.sqlite
SQLite format 3
triggerfile_update_triggerfileCREATE TRIGGER file_update_trigger AFTER UPDATE ON file FOR EACH ROW WHEN NEW.refcount = 0 BEGIN DELETE FROM file WHERE id = OLD.id; END
Ytriggerobject_data_delete_triggerobject_dataCREATE TRIGGER object_data_delete_trigger AFTER DELETE ON object_data FOR EACH ROW WHEN OLD.file_ids IS NOT NULL BEGIN SELECT update_refcount(OLD.file_ids, NULL); END
9triggerobject_data_update_triggerobject_dataCREATE TRIGGER object_data_update_trigger AFTER UPDATE OF file_ids ON object_data FOR EACH ROW WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL BEGIN SELECT update_refcount(OLD.file_ids, NEW.file_ids); END
Ytriggerobject_data_insert_triggerobject_dataCREATE TRIGGER object_data_insert_trigger AFTER INSERT ON object_data FOR EACH ROW WHEN NEW.file_ids IS NOT NULL BEGIN SELECT update_refcount(NULL, NEW.file_ids); ENDZ
tablefilefile
CREATE TABLE file (id INTEGER PRIMARY KEY, refcount INTEGER NOT NULL)
9indexunique_index_data_value_locale_indexunique_index_data
CREATE INDEX unique_index_data_value_locale_index ON unique_index_data (index_id, value_locale, object_data_key, value) WHERE value_locale IS NOT NULL
_tableunique_index_dataunique_index_data        CREATE TABLE unique_index_data( index_id INTEGER NOT NULL, value BLOB NOT NULL, object_store_id INTEGER NOT NULL, object_data_key BLOB NOT NULL, value_locale BLOB, PRIMARY KEY (index_id, value), FOREIGN KEY (index_id) REFERENCES object_store_index(id) , FOREIGN KEY (object_store_id, object_data_key) REFERENCES object_data(object_store_id, key) ) WITHOUT ROWID
indexindex_data_value_locale_indexindex_data
CREATE INDEX index_data_value_locale_index ON index_data (index_id, value_locale, object_data_key, value) WHERE value_locale IS NOT NULL
stableindex_dataindex_data
CREATE TABLE index_data( index_id INTEGER NOT NULL, value BLOB NOT NULL, object_data_key BLOB NOT NULL, object_store_id INTEGER NOT NULL, value_locale BLOB, PRIMARY KEY (index_id, value, object_data_key), FOREIGN KEY (index_id) REFERENCES object_store_index(id) , FOREIGN KEY (object_store_id, object_data_key) REFERENCES object_data(object_store_id, key) ) WITHOUT ROWID
tableobject_dataobject_data
CREATE TABLE object_data( object_store_id INTEGER NOT NULL, key BLOB NOT NULL, index_data_values BLOB DEFAULT NULL, file_ids TEXT, data BLOB NOT NULL, PRIMARY KEY (object_store_id, key), FOREIGN KEY (object_store_id) REFERENCES object_store(id) ) WITHOUT ROWID
etableobject_store_indexobject_store_index
CREATE TABLE object_store_index( id INTEGER PRIMARY KEY, object_store_id INTEGER NOT NULL, name TEXT NOT NULL, key_path TEXT NOT NULL, unique_index INTEGER NOT NULL, multientry INTEGER NOT NULL, locale TEXT, is_auto_locale BOOLEAN NOT NULL, FOREIGN KEY (object_store_id) REFERENCES object_store(id) )
tableobject_storeobject_store
CREATE TABLE object_store( id INTEGER PRIMARY KEY, auto_increment INTEGER NOT NULL DEFAULT 0, name TEXT NOT NULL, key_path TEXT)
tabledatabasedatabase
CREATE TABLE database( name TEXT PRIMARY KEY, origin TEXT NOT NULL, version INTEGER NOT NULL DEFAULT 0, last_vacuum_time INTEGER NOT NULL DEFAULT 0, last_analyze_time INTEGER NOT NULL DEFAULT 0, last_vacuum_size INTEGER NOT NULL DEFAULT 0) WITHOUT ROWID
persistenthttps://firefox-storage-test.glitch.me

At this point we have verified that:

  • The file/directory names to not include the (real) origin name in the clear.
  • That the metadata file likewise does not contain the (real) origin name in the clear.
  • That the file is encrypted although it includes the expected SQLite header.

IDB Blob Encryption

Our IndexedDB implementation stores Blob/File instances in individual files in the ".files"-suffixed subdirectory. Values stored in IDB may also be "spilled" to disk, currently when they exceed 1 megabyte in size. We want to verify that these are also encrypted.

The steps in this section continue from the previous section. It's assumed we're using the same already-open Private Browsing window.

  1. Browse the tab, or a new tab is also okay, to https://firefox-storage-test.glitch.me/blobs.html which opens a database "blobs" and adds the contents of the MIT licensed hunspell dict found at https://github.com/wooorm/dictionaries/blob/main/dictionaries/en/index.dic but hosted via glitch to avoid breakage. Assuming everything is working the string "Created a Blob and stored it in the DB and now holding the database open." should be displayed and every 1 seconds a console.log should list the keys of the blobs that exist in the database. The 1 second interval should avoid the .sqlite-wal being checkpointed and truncated. Every time the page is reloaded a new copy of the Blob is added which may or may not be useful.
  2. Verify that a blob file has been created. We should see the new database with a new UUID plus an open .sqlite-wal version of the database, as well as a file named 1 under the .files-suffixed directory. The .files directory should also contain a journals subdirectory. Invoking find, I see the following:
$ /usr/bin/find .
.
./uuid+++d0931324-3b24-4e37-a618-18030cd28373
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/.metadata-v2
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.files
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.files/1
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.files/journals
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/ba95b3a6-1619-4b54-8169-1fa916483fc1.sqlite
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.sqlite-wal
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/ba95b3a6-1619-4b54-8169-1fa916483fc1.files
./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.sqlite
  1. Verify that the WAL file is encrypted by using the ent command. Note that if the IDB subsystem decides to close the database connection, it will checkpoint the WAL and truncate the file, leaving it with a 0 file length and the command will say "Entropy = 0.000000 bits per byte." and "Optimum compression would reduce the size of this 0 byte file by 100 percent." over 2 lines. My example successful run:
$ ent ./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.sqlite-wal
Entropy = 7.987535 bits per byte.

Optimum compression would reduce the size
of this 16464 byte file by 0 percent.

Chi square distribution for 16464 samples is 293.43, and randomly
would exceed this value 4.93 percent of the times.

Arithmetic mean value of data bytes is 127.0557 (127.5 = random).
Monte Carlo value for Pi is 3.183673469 (error 1.34 percent).
Serial correlation coefficient is 0.012650 (totally uncorrelated = 0.0).
  1. Verify that the Blob file is encrypted by using the ent command. Attention should also be paid that the file size is around 561k. (The source file is 551k and we don't currently compress blob payloads.) My run:
$ ent ./uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.files/1
Entropy = 7.996719 bits per byte.

Optimum compression would reduce the size
of this 561152 byte file by 0 percent.

Chi square distribution for 561152 samples is 3090.62, and randomly
would exceed this value less than 0.01 percent of the times.

Arithmetic mean value of data bytes is 127.6357 (127.5 = random).
Monte Carlo value for Pi is 3.123015237 (error 0.59 percent).
Serial correlation coefficient is 0.012496 (totally uncorrelated = 0.0).

Verify normal cleanup from closing the PB window.

  1. Change your current directory to PROFILE/storage so we can ensure that your working directory does not interfere with any cleanup mechanisms. For the remainder of this document, we'll continue to work from this directory.
  2. First ensuring that you have a normal (non-PB) window open in addition to the PB window, close the private browsing window we opened above and wait a few seconds.
  3. Verify that the "private" directory no longer exists under PROFILE/storage.

Verify normal cleanup when quitting

  1. Open a new private browsing window.
  2. Open https://firefox-storage-test.glitch.me/ in the tab that open.
  3. Open https://firefox-storage-test.glitch.me/blobs.html in an additional tab.
  4. From PROFILE/storage verify that the expected files are created using find, for example:
$ /usr/bin/find private
private
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373/.metadata-v2
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.files
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.files/1
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.files/journals
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/ba95b3a6-1619-4b54-8169-1fa916483fc1.sqlite
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.sqlite-wal
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/ba95b3a6-1619-4b54-8169-1fa916483fc1.files
private/uuid+++d0931324-3b24-4e37-a618-18030cd28373/idb/44a02ec5-371e-411c-ae8c-11611d254fbf.sqlite
  1. Use the hamburger menu to choose "Quit".
  2. Verify that the "private" subdirectory no longer exists. Ex, with find:
$ /usr/bin/find private
/usr/bin/find: ‘private’: No such file or directory
  1. Observe if a "to-be-removed" subdirectory exists and verify that if it exists it is empty. Using find:
$ /usr/bin/find to-be-removed
to-be-removed

Verify normal cleanup when only a PB window exists and is closed

  1. Start Firefox back up.
  2. Open a private browsing window.
  3. Close the normal, non-private browsing window.
  4. Open https://firefox-storage-test.glitch.me/ in the tab that open.
  5. Open https://firefox-storage-test.glitch.me/blobs.html in an additional tab.
  6. Verify the state of the "private" directory is as expected using find:
$ /usr/bin/find private
private
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b/.metadata-v2
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b/idb
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b/idb/1bec8935-40bd-493e-8126-7273f0087839.sqlite
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b/idb/5b7c3ae6-3973-4b27-a2f2-1974f8b128f1.sqlite
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b/idb/1bec8935-40bd-493e-8126-7273f0087839.files
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b/idb/1bec8935-40bd-493e-8126-7273f0087839.files/1
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b/idb/1bec8935-40bd-493e-8126-7273f0087839.files/journals
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b/idb/1bec8935-40bd-493e-8126-7273f0087839.sqlite-wal
private/uuid+++324097b6-afed-40d7-8d66-5de3d610dd0b/idb/5b7c3ae6-3973-4b27-a2f2-1974f8b128f1.files
  1. Close the window via the close button for the window, not using the quit menu option.
  2. Verify that the "private" subdirectory no longer exists. Ex, with find:
$ /usr/bin/find private
/usr/bin/find: ‘private’: No such file or directory
  1. Observe if a "to-be-removed" subdirectory exists and verify that if it exists it is empty. Using find:
$ /usr/bin/find to-be-removed
to-be-removed

Verify cleanup on next start after a crash

  1. Start Firefox back up, noting that we will want to forcibly terminate the browser and you may want to plan for that. In particular, it's better to specify
  2. Open a private browsing window.
  3. Open https://firefox-storage-test.glitch.me/ in the tab that open.
  4. Open https://firefox-storage-test.glitch.me/blobs.html in an additional tab.
  5. Verify the state of the "private" directory is as expected using find:
private
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/.metadata-v2
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.sqlite-wal
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.sqlite
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/cf8ed64f-31ed-4a3a-98d6-840a3f0ee996.files
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/cf8ed64f-31ed-4a3a-98d6-840a3f0ee996.sqlite
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.files
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.files/1
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.files/journals
  1. Identify the process to terminate. This can be a bit involved if you have other instances of firefox running that you don't want to terminate. On linux at least, the easiest option is to make sure you invoke firefox using an absolute path which then makes it easier to do ps uax | grep SOMETHING where "SOMETHING" would be a unique path segment to the version of firefox under test. The process should normally be numerically first (unless there's a pid wraparound) and will NEVER include "-contentproc", so you can use an inverted grep to exclude lines with it, like ps uax | grep /path/to/firefox | grep -v contentproc.
  2. Terminate the Firefox process using kill -9 or similar so there's no chance for an orderly shutdown.
  3. Verify that the "private" directory is still there and still contains the same contents because Firefox didn't have a chance to clean up. (Note that in the future it's possible some kind of crash monitoring or handling might step in and do something, but on linux that's not happening right now.) Using find:
private
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/.metadata-v2
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.sqlite-wal
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.sqlite
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/cf8ed64f-31ed-4a3a-98d6-840a3f0ee996.files
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/cf8ed64f-31ed-4a3a-98d6-840a3f0ee996.sqlite
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.files
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.files/1
private/uuid+++e47e1c94-db03-4d1c-aed1-01e0485e1948/idb/debcaa47-9d9d-4132-aba9-7bd458fd79a7.files/journals
  1. Start Firefox up again and wait a few seconds.
  2. Verify that the "private" directory disappeared using find:
$ /usr/bin/find private
/usr/bin/find: ‘private’: No such file or directory
  1. Verify that "to-be-removed" is empty if present using find:
$ /usr/bin/find to-be-removed
to-be-removed

Do a happy dance!

Although not compulsory, the following steps can be followed in recognition of what a major step forward this is:

  1. Do a celebratory dance. Other options include performing a celebratory fist pump.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment